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.car.notification; 18 19 import static android.app.PendingIntent.FLAG_IMMUTABLE; 20 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; 21 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; 22 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import static org.mockito.ArgumentMatchers.anyInt; 27 import static org.mockito.ArgumentMatchers.anyString; 28 import static org.mockito.Mockito.mock; 29 import static org.mockito.Mockito.never; 30 import static org.mockito.Mockito.times; 31 import static org.mockito.Mockito.verify; 32 import static org.mockito.Mockito.when; 33 34 import android.app.Notification; 35 import android.app.NotificationChannel; 36 import android.app.PendingIntent; 37 import android.car.drivingstate.CarUxRestrictions; 38 import android.content.Intent; 39 import android.content.pm.ApplicationInfo; 40 import android.content.pm.PackageInfo; 41 import android.content.pm.PackageManager; 42 import android.os.Bundle; 43 import android.os.UserHandle; 44 import android.service.notification.NotificationListenerService; 45 import android.service.notification.SnoozeCriterion; 46 import android.service.notification.StatusBarNotification; 47 import android.telephony.TelephonyManager; 48 import android.testing.TestableContext; 49 import android.testing.TestableResources; 50 import android.text.TextUtils; 51 52 import androidx.test.ext.junit.runners.AndroidJUnit4; 53 import androidx.test.platform.app.InstrumentationRegistry; 54 55 import org.junit.Before; 56 import org.junit.Rule; 57 import org.junit.Test; 58 import org.junit.runner.RunWith; 59 import org.mockito.ArgumentCaptor; 60 import org.mockito.InOrder; 61 import org.mockito.Mock; 62 import org.mockito.Mockito; 63 import org.mockito.MockitoAnnotations; 64 65 import java.util.ArrayList; 66 import java.util.Arrays; 67 import java.util.HashMap; 68 import java.util.HashSet; 69 import java.util.List; 70 import java.util.Map; 71 import java.util.Set; 72 import java.util.function.Function; 73 import java.util.stream.Collectors; 74 75 @RunWith(AndroidJUnit4.class) 76 public class PreprocessingManagerTest { 77 78 private static final String PKG = "com.package.PREPROCESSING_MANAGER_TEST"; 79 private static final String OP_PKG = "OpPackage"; 80 private static final int ID = 1; 81 private static final String TAG = "Tag"; 82 private static final int UID = 2; 83 private static final int INITIAL_PID = 3; 84 private static final String CHANNEL_ID = "CHANNEL_ID"; 85 private static final String CONTENT_TITLE = "CONTENT_TITLE"; 86 private static final String OVERRIDE_GROUP_KEY = "OVERRIDE_GROUP_KEY"; 87 private static final long POST_TIME = 12345l; 88 private static final UserHandle USER_HANDLE = new UserHandle(12); 89 private static final String GROUP_KEY_A = "GROUP_KEY_A"; 90 private static final String GROUP_KEY_B = "GROUP_KEY_B"; 91 private static final String GROUP_KEY_C = "GROUP_KEY_C"; 92 private static final int MAX_STRING_LENGTH = 10; 93 private static final int DEFAULT_MIN_GROUPING_THRESHOLD = 4; 94 @Rule 95 public final TestableContext mContext = new TestableContext( 96 InstrumentationRegistry.getInstrumentation().getTargetContext()); 97 @Mock 98 private StatusBarNotification mStatusBarNotification1; 99 @Mock 100 private StatusBarNotification mStatusBarNotification2; 101 @Mock 102 private StatusBarNotification mStatusBarNotification3; 103 @Mock 104 private StatusBarNotification mStatusBarNotification4; 105 @Mock 106 private StatusBarNotification mStatusBarNotification5; 107 @Mock 108 private StatusBarNotification mStatusBarNotification6; 109 @Mock 110 private StatusBarNotification mStatusBarNotification7; 111 @Mock 112 private StatusBarNotification mStatusBarNotification8; 113 @Mock 114 private StatusBarNotification mStatusBarNotification9; 115 @Mock 116 private StatusBarNotification mStatusBarNotification10; 117 @Mock 118 private StatusBarNotification mStatusBarNotification11; 119 @Mock 120 private StatusBarNotification mStatusBarNotification12; 121 @Mock 122 private StatusBarNotification mAdditionalStatusBarNotification; 123 @Mock 124 private StatusBarNotification mSummaryAStatusBarNotification; 125 @Mock 126 private StatusBarNotification mSummaryBStatusBarNotification; 127 @Mock 128 private StatusBarNotification mSummaryCStatusBarNotification; 129 @Mock 130 private CarUxRestrictions mCarUxRestrictions; 131 @Mock 132 private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper; 133 @Mock 134 private PreprocessingManager.CallStateListener mCallStateListener1; 135 @Mock 136 private PreprocessingManager.CallStateListener mCallStateListener2; 137 @Mock 138 private Notification mMediaNotification; 139 @Mock 140 private Notification mSummaryNotification; 141 @Mock 142 private PackageManager mPackageManager; 143 @Mock 144 private NotificationDataManager mNotificationDataManager; 145 146 private PreprocessingManager mPreprocessingManager; 147 148 private Notification mForegroundNotification; 149 private Notification mBackgroundNotification; 150 private Notification mNavigationNotification; 151 152 // Following AlertEntry var names describe the type of notifications they wrap. 153 private AlertEntry mLessImportantBackground; 154 private AlertEntry mLessImportantForeground; 155 private AlertEntry mMedia; 156 private AlertEntry mNavigation; 157 private AlertEntry mImportantBackground; 158 private AlertEntry mImportantForeground; 159 private AlertEntry mImportantForeground2; 160 private AlertEntry mImportantForeground3; 161 private AlertEntry mImportantForeground4; 162 private AlertEntry mImportantForeground5; 163 private AlertEntry mImportantForeground6; 164 private AlertEntry mImportantForeground7; 165 166 private List<AlertEntry> mAlertEntries; 167 private Map<String, AlertEntry> mAlertEntriesMap; 168 private NotificationListenerService.RankingMap mRankingMap; 169 170 @Before setup()171 public void setup() throws PackageManager.NameNotFoundException { 172 MockitoAnnotations.initMocks(this); 173 174 // prevents less important foreground notifications from not being filtered due to the 175 // application and package setup. 176 PackageInfo packageInfo = mock(PackageInfo.class); 177 ApplicationInfo applicationInfo = mock(ApplicationInfo.class); 178 packageInfo.packageName = PKG; 179 when(applicationInfo.isPrivilegedApp()).thenReturn(true); 180 when(applicationInfo.isSystemApp()).thenReturn(true); 181 when(applicationInfo.isSignedWithPlatformKey()).thenReturn(true); 182 packageInfo.applicationInfo = applicationInfo; 183 when(mPackageManager.getPackageInfoAsUser(anyString(), anyInt(), anyInt())).thenReturn( 184 packageInfo); 185 mContext.setMockPackageManager(mPackageManager); 186 187 mPreprocessingManager.refreshInstance(); 188 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 189 190 mForegroundNotification = generateNotification(/* isForeground= */ true, 191 /* isNavigation= */ false, /* isGroupSummary= */ true); 192 mBackgroundNotification = generateNotification(/* isForeground= */ false, 193 /* isNavigation= */ false, /* isGroupSummary= */ true); 194 mNavigationNotification = generateNotification(/* isForeground= */ true, 195 /* isNavigation= */ true, /* isGroupSummary= */ true); 196 197 when(mMediaNotification.isMediaNotification()).thenReturn(true); 198 199 // Key describes the notification that the StatusBarNotification contains. 200 when(mStatusBarNotification1.getKey()).thenReturn("KEY_LESS_IMPORTANT_BACKGROUND"); 201 when(mStatusBarNotification2.getKey()).thenReturn("KEY_LESS_IMPORTANT_FOREGROUND"); 202 when(mStatusBarNotification3.getKey()).thenReturn("KEY_MEDIA"); 203 when(mStatusBarNotification4.getKey()).thenReturn("KEY_NAVIGATION"); 204 when(mStatusBarNotification5.getKey()).thenReturn("KEY_IMPORTANT_BACKGROUND"); 205 when(mStatusBarNotification6.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND"); 206 when(mStatusBarNotification7.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_2"); 207 when(mStatusBarNotification8.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_3"); 208 when(mStatusBarNotification9.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_4"); 209 when(mStatusBarNotification10.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_5"); 210 when(mStatusBarNotification11.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_6"); 211 when(mStatusBarNotification12.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_7"); 212 when(mSummaryAStatusBarNotification.getKey()).thenReturn("KEY_SUMMARY_A"); 213 when(mSummaryBStatusBarNotification.getKey()).thenReturn("KEY_SUMMARY_B"); 214 when(mSummaryCStatusBarNotification.getKey()).thenReturn("KEY_SUMMARY_C"); 215 216 when(mStatusBarNotification1.getGroupKey()).thenReturn(GROUP_KEY_A); 217 when(mStatusBarNotification2.getGroupKey()).thenReturn(GROUP_KEY_B); 218 when(mStatusBarNotification3.getGroupKey()).thenReturn(GROUP_KEY_A); 219 when(mStatusBarNotification4.getGroupKey()).thenReturn(GROUP_KEY_B); 220 when(mStatusBarNotification5.getGroupKey()).thenReturn(GROUP_KEY_B); 221 when(mStatusBarNotification6.getGroupKey()).thenReturn(GROUP_KEY_C); 222 when(mSummaryAStatusBarNotification.getGroupKey()).thenReturn(GROUP_KEY_A); 223 when(mSummaryBStatusBarNotification.getGroupKey()).thenReturn(GROUP_KEY_B); 224 when(mSummaryCStatusBarNotification.getGroupKey()).thenReturn(GROUP_KEY_C); 225 226 when(mStatusBarNotification1.getNotification()).thenReturn(mBackgroundNotification); 227 when(mStatusBarNotification2.getNotification()).thenReturn(mForegroundNotification); 228 when(mStatusBarNotification3.getNotification()).thenReturn(mMediaNotification); 229 when(mStatusBarNotification4.getNotification()).thenReturn(mNavigationNotification); 230 when(mStatusBarNotification5.getNotification()).thenReturn(mBackgroundNotification); 231 when(mStatusBarNotification6.getNotification()).thenReturn(mForegroundNotification); 232 when(mStatusBarNotification7.getNotification()).thenReturn(mForegroundNotification); 233 when(mStatusBarNotification8.getNotification()).thenReturn(mForegroundNotification); 234 when(mStatusBarNotification9.getNotification()).thenReturn(mForegroundNotification); 235 when(mStatusBarNotification10.getNotification()).thenReturn(mForegroundNotification); 236 when(mStatusBarNotification11.getNotification()).thenReturn(mForegroundNotification); 237 when(mStatusBarNotification12.getNotification()).thenReturn(mForegroundNotification); 238 when(mSummaryAStatusBarNotification.getNotification()).thenReturn(mSummaryNotification); 239 when(mSummaryBStatusBarNotification.getNotification()).thenReturn(mSummaryNotification); 240 when(mSummaryCStatusBarNotification.getNotification()).thenReturn(mSummaryNotification); 241 242 when(mStatusBarNotification1.getPackageName()).thenReturn(PKG); 243 when(mStatusBarNotification2.getPackageName()).thenReturn(PKG); 244 when(mStatusBarNotification3.getPackageName()).thenReturn(PKG); 245 when(mStatusBarNotification4.getPackageName()).thenReturn(PKG); 246 when(mStatusBarNotification5.getPackageName()).thenReturn(PKG); 247 when(mStatusBarNotification6.getPackageName()).thenReturn(PKG); 248 when(mStatusBarNotification7.getPackageName()).thenReturn(PKG); 249 when(mStatusBarNotification8.getPackageName()).thenReturn(PKG); 250 when(mStatusBarNotification9.getPackageName()).thenReturn(PKG); 251 when(mStatusBarNotification10.getPackageName()).thenReturn(PKG); 252 when(mStatusBarNotification11.getPackageName()).thenReturn(PKG); 253 when(mStatusBarNotification12.getPackageName()).thenReturn(PKG); 254 when(mSummaryAStatusBarNotification.getPackageName()).thenReturn(PKG); 255 when(mSummaryBStatusBarNotification.getPackageName()).thenReturn(PKG); 256 when(mSummaryCStatusBarNotification.getPackageName()).thenReturn(PKG); 257 258 when(mSummaryNotification.isGroupSummary()).thenReturn(true); 259 260 // Always start system with no phone calls in progress. 261 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 262 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_IDLE); 263 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 264 265 initTestData(/* includeAdditionalNotifs= */ false); 266 } 267 268 @Test onFilter_showLessImportantNotifications_doesNotFilterNotifications()269 public void onFilter_showLessImportantNotifications_doesNotFilterNotifications() { 270 List<AlertEntry> unfiltered = mAlertEntries.stream().collect(Collectors.toList()); 271 mPreprocessingManager 272 .filter(/* showLessImportantNotifications= */ true, mAlertEntries, mRankingMap); 273 274 assertThat(mAlertEntries.equals(unfiltered)).isTrue(); 275 } 276 277 @Test onFilter_dontShowLessImportantNotifications_filtersLessImportantForeground()278 public void onFilter_dontShowLessImportantNotifications_filtersLessImportantForeground() 279 throws PackageManager.NameNotFoundException { 280 mPreprocessingManager 281 .filter( /* showLessImportantNotifications= */ false, mAlertEntries, mRankingMap); 282 283 assertThat(mAlertEntries.contains(mLessImportantBackground)).isTrue(); 284 assertThat(mAlertEntries.contains(mLessImportantForeground)).isFalse(); 285 } 286 287 @Test onFilter_dontShowLessImportantNotifications_doesNotFilterMoreImportant()288 public void onFilter_dontShowLessImportantNotifications_doesNotFilterMoreImportant() { 289 mPreprocessingManager 290 .filter(/* showLessImportantNotifications= */ false, mAlertEntries, mRankingMap); 291 292 assertThat(mAlertEntries.contains(mImportantBackground)).isTrue(); 293 assertThat(mAlertEntries.contains(mImportantForeground)).isTrue(); 294 } 295 296 @Test onFilter_dontShowLessImportantNotifications_filtersMediaAndNavigation()297 public void onFilter_dontShowLessImportantNotifications_filtersMediaAndNavigation() { 298 mPreprocessingManager 299 .filter(/* showLessImportantNotifications= */ false, mAlertEntries, mRankingMap); 300 301 assertThat(mAlertEntries.contains(mMedia)).isFalse(); 302 assertThat(mAlertEntries.contains(mNavigation)).isFalse(); 303 } 304 305 @Test onFilter_doShowLessImportantNotifications_doesNotFilterMediaOrNavigation()306 public void onFilter_doShowLessImportantNotifications_doesNotFilterMediaOrNavigation() { 307 mPreprocessingManager 308 .filter(/* showLessImportantNotifications= */ true, mAlertEntries, mRankingMap); 309 310 assertThat(mAlertEntries.contains(mMedia)).isTrue(); 311 assertThat(mAlertEntries.contains(mNavigation)).isTrue(); 312 } 313 314 @Test onFilter_doShowLessImportantNotifications_filtersCalls()315 public void onFilter_doShowLessImportantNotifications_filtersCalls() { 316 StatusBarNotification callSBN = mock(StatusBarNotification.class); 317 Notification callNotification = new Notification(); 318 callNotification.category = Notification.CATEGORY_CALL; 319 when(callSBN.getNotification()).thenReturn(callNotification); 320 List<AlertEntry> entries = new ArrayList<>(); 321 entries.add(new AlertEntry(callSBN)); 322 323 mPreprocessingManager.filter(true, entries, mRankingMap); 324 assertThat(entries).isEmpty(); 325 } 326 327 @Test onFilter_dontShowLessImportantNotifications_filtersCalls()328 public void onFilter_dontShowLessImportantNotifications_filtersCalls() { 329 StatusBarNotification callSBN = mock(StatusBarNotification.class); 330 Notification callNotification = new Notification(); 331 callNotification.category = Notification.CATEGORY_CALL; 332 when(callSBN.getNotification()).thenReturn(callNotification); 333 List<AlertEntry> entries = new ArrayList<>(); 334 entries.add(new AlertEntry(callSBN)); 335 336 mPreprocessingManager.filter(false, entries, mRankingMap); 337 assertThat(entries).isEmpty(); 338 } 339 340 @Test onOptimizeForDriving_alertEntryHasNonMessageNotification_trimsNotificationTexts()341 public void onOptimizeForDriving_alertEntryHasNonMessageNotification_trimsNotificationTexts() { 342 when(mCarUxRestrictions.getMaxRestrictedStringLength()).thenReturn(MAX_STRING_LENGTH); 343 when(mCarUxRestrictionManagerWrapper.getCurrentCarUxRestrictions()) 344 .thenReturn(mCarUxRestrictions); 345 mPreprocessingManager.setCarUxRestrictionManagerWrapper(mCarUxRestrictionManagerWrapper); 346 347 Notification nonMessageNotification = generateNotification(/* isForeground= */ true, 348 /* isNavigation= */ true, /* isGroupSummary= */ true); 349 nonMessageNotification.extras 350 .putString(Notification.EXTRA_TITLE, generateStringOfLength(100)); 351 nonMessageNotification.extras 352 .putString(Notification.EXTRA_TEXT, generateStringOfLength(100)); 353 nonMessageNotification.extras 354 .putString(Notification.EXTRA_TITLE_BIG, generateStringOfLength(100)); 355 nonMessageNotification.extras 356 .putString(Notification.EXTRA_SUMMARY_TEXT, generateStringOfLength(100)); 357 358 when(mNavigation.getNotification()).thenReturn(nonMessageNotification); 359 360 AlertEntry optimized = mPreprocessingManager.optimizeForDriving(mNavigation); 361 Bundle trimmed = optimized.getNotification().extras; 362 363 for (String key : trimmed.keySet()) { 364 switch (key) { 365 case Notification.EXTRA_TITLE: 366 case Notification.EXTRA_TEXT: 367 case Notification.EXTRA_TITLE_BIG: 368 case Notification.EXTRA_SUMMARY_TEXT: 369 CharSequence text = trimmed.getCharSequence(key); 370 assertThat(text.length() <= MAX_STRING_LENGTH).isTrue(); 371 default: 372 continue; 373 } 374 } 375 } 376 377 @Test onOptimizeForDriving_alertEntryHasMessageNotification_doesNotTrimMessageTexts()378 public void onOptimizeForDriving_alertEntryHasMessageNotification_doesNotTrimMessageTexts() { 379 when(mCarUxRestrictions.getMaxRestrictedStringLength()).thenReturn(MAX_STRING_LENGTH); 380 when(mCarUxRestrictionManagerWrapper.getCurrentCarUxRestrictions()) 381 .thenReturn(mCarUxRestrictions); 382 mPreprocessingManager.setCarUxRestrictionManagerWrapper(mCarUxRestrictionManagerWrapper); 383 384 Notification messageNotification = generateNotification(/* isForeground= */ true, 385 /* isNavigation= */ true, /* isGroupSummary= */ true); 386 messageNotification.extras 387 .putString(Notification.EXTRA_TITLE, generateStringOfLength(100)); 388 messageNotification.extras 389 .putString(Notification.EXTRA_TEXT, generateStringOfLength(100)); 390 messageNotification.extras 391 .putString(Notification.EXTRA_TITLE_BIG, generateStringOfLength(100)); 392 messageNotification.extras 393 .putString(Notification.EXTRA_SUMMARY_TEXT, generateStringOfLength(100)); 394 messageNotification.category = Notification.CATEGORY_MESSAGE; 395 396 when(mImportantForeground.getNotification()).thenReturn(messageNotification); 397 398 AlertEntry optimized = mPreprocessingManager.optimizeForDriving(mImportantForeground); 399 Bundle trimmed = optimized.getNotification().extras; 400 401 for (String key : trimmed.keySet()) { 402 switch (key) { 403 case Notification.EXTRA_TITLE: 404 case Notification.EXTRA_TEXT: 405 case Notification.EXTRA_TITLE_BIG: 406 case Notification.EXTRA_SUMMARY_TEXT: 407 CharSequence text = trimmed.getCharSequence(key); 408 assertThat(text.length() <= MAX_STRING_LENGTH).isFalse(); 409 default: 410 continue; 411 } 412 } 413 } 414 415 @Test onGroup_groupsNotificationsByGroupKey()416 public void onGroup_groupsNotificationsByGroupKey() { 417 setConfig(/* recentOld= */ true, /* launcherIcon= */ true, /* groupingThreshold= */ 2); 418 PreprocessingManager.refreshInstance(); 419 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 420 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 421 String[] actualGroupKeys = new String[groupResult.size()]; 422 String[] expectedGroupKeys = {GROUP_KEY_A, GROUP_KEY_B, GROUP_KEY_C}; 423 424 for (int i = 0; i < groupResult.size(); i++) { 425 actualGroupKeys[i] = groupResult.get(i).getGroupKey(); 426 } 427 428 Arrays.sort(actualGroupKeys); 429 Arrays.sort(expectedGroupKeys); 430 431 assertThat(actualGroupKeys).isEqualTo(expectedGroupKeys); 432 } 433 434 @Test onGroup_highGroupingThreshold_noGroups()435 public void onGroup_highGroupingThreshold_noGroups() { 436 setConfig(/* recentOld= */ true, /* launcherIcon= */ true, DEFAULT_MIN_GROUPING_THRESHOLD); 437 PreprocessingManager.refreshInstance(); 438 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 439 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 440 String[] actualGroupKeys = new String[groupResult.size()]; 441 String[] expectedGroupKeys = {GROUP_KEY_A, GROUP_KEY_B, GROUP_KEY_B, GROUP_KEY_C}; 442 443 for (int i = 0; i < groupResult.size(); i++) { 444 actualGroupKeys[i] = groupResult.get(i).getGroupKey(); 445 } 446 447 Arrays.sort(actualGroupKeys); 448 Arrays.sort(expectedGroupKeys); 449 450 assertThat(actualGroupKeys).isEqualTo(expectedGroupKeys); 451 } 452 453 @Test onGroup_groupsNotificationsBySeenUnseen()454 public void onGroup_groupsNotificationsBySeenUnseen() { 455 setConfig(/* recentOld= */ true, /* launcherIcon= */ true, DEFAULT_MIN_GROUPING_THRESHOLD); 456 initTestData(/* includeAdditionalNotifs= */ true); 457 PreprocessingManager.refreshInstance(); 458 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 459 when(mNotificationDataManager.isNotificationSeen(mLessImportantForeground)) 460 .thenReturn(true); 461 when(mNotificationDataManager.isNotificationSeen(mLessImportantBackground)) 462 .thenReturn(true); 463 when(mNotificationDataManager.isNotificationSeen(mMedia)).thenReturn(true); 464 when(mNotificationDataManager.isNotificationSeen(mImportantBackground)).thenReturn(true); 465 when(mNotificationDataManager.isNotificationSeen(mImportantForeground)).thenReturn(true); 466 when(mNotificationDataManager.isNotificationSeen(mImportantForeground2)).thenReturn(true); 467 when(mNotificationDataManager.isNotificationSeen(mImportantForeground3)).thenReturn(true); 468 when(mNotificationDataManager.isNotificationSeen(mImportantForeground4)).thenReturn(false); 469 when(mNotificationDataManager.isNotificationSeen(mImportantForeground5)).thenReturn(false); 470 when(mNotificationDataManager.isNotificationSeen(mImportantForeground6)).thenReturn(false); 471 when(mNotificationDataManager.isNotificationSeen(mImportantForeground7)).thenReturn(false); 472 when(mNotificationDataManager.isNotificationSeen(mNavigation)).thenReturn(false); 473 when(mStatusBarNotification1.getGroupKey()).thenReturn(GROUP_KEY_A); 474 when(mStatusBarNotification2.getGroupKey()).thenReturn(GROUP_KEY_A); 475 when(mStatusBarNotification3.getGroupKey()).thenReturn(GROUP_KEY_A); 476 when(mStatusBarNotification4.getGroupKey()).thenReturn(GROUP_KEY_A); 477 when(mStatusBarNotification5.getGroupKey()).thenReturn(GROUP_KEY_A); 478 when(mStatusBarNotification6.getGroupKey()).thenReturn(GROUP_KEY_A); 479 when(mStatusBarNotification7.getGroupKey()).thenReturn(GROUP_KEY_A); 480 when(mStatusBarNotification8.getGroupKey()).thenReturn(GROUP_KEY_A); 481 when(mStatusBarNotification9.getGroupKey()).thenReturn(GROUP_KEY_A); 482 when(mStatusBarNotification10.getGroupKey()).thenReturn(GROUP_KEY_A); 483 when(mStatusBarNotification11.getGroupKey()).thenReturn(GROUP_KEY_A); 484 when(mStatusBarNotification12.getGroupKey()).thenReturn(GROUP_KEY_A); 485 486 mPreprocessingManager.setNotificationDataManager(mNotificationDataManager); 487 488 Set expectedResultUnseen = new HashSet(); 489 expectedResultUnseen.add(mImportantBackground.getKey()); 490 expectedResultUnseen.add(mNavigation.getKey()); 491 expectedResultUnseen.add(mImportantForeground4.getKey()); 492 expectedResultUnseen.add(mImportantForeground5.getKey()); 493 expectedResultUnseen.add(mImportantForeground6.getKey()); 494 expectedResultUnseen.add(mImportantForeground7.getKey()); 495 Set expectedResultSeen = new HashSet(); 496 expectedResultSeen.add(mImportantBackground.getKey()); 497 expectedResultSeen.add(mLessImportantForeground.getKey()); 498 expectedResultSeen.add(mImportantForeground2.getKey()); 499 expectedResultSeen.add(mImportantForeground3.getKey()); 500 expectedResultSeen.add(mMedia.getKey()); 501 expectedResultSeen.add(mImportantForeground.getKey()); 502 503 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 504 Set actualResultSeen = new HashSet(); 505 Set actualResultUnseen = new HashSet(); 506 for (int j = 0; j < groupResult.size(); j++) { 507 NotificationGroup group = groupResult.get(j); 508 List<AlertEntry> childNotifications = group.getChildNotifications(); 509 for (int i = 0; i < childNotifications.size(); i++) { 510 if (group.isSeen()) { 511 actualResultSeen.add(childNotifications.get(i).getKey()); 512 } else { 513 actualResultUnseen.add(childNotifications.get(i).getKey()); 514 } 515 } 516 if (group.getGroupSummaryNotification() != null) { 517 if (group.isSeen()) { 518 actualResultSeen.add(group.getGroupSummaryNotification().getKey()); 519 } else { 520 actualResultUnseen.add(group.getGroupSummaryNotification().getKey()); 521 } 522 } 523 } 524 assertThat(actualResultSeen).isEqualTo(expectedResultSeen); 525 assertThat(actualResultUnseen).isEqualTo(expectedResultUnseen); 526 } 527 528 @Test onGroup_autoGeneratedGroupWithNoGroupChildren_doesNotShowGroupSummary()529 public void onGroup_autoGeneratedGroupWithNoGroupChildren_doesNotShowGroupSummary() { 530 List<AlertEntry> list = new ArrayList<>(); 531 list.add(getEmptyAutoGeneratedGroupSummary()); 532 List<NotificationGroup> groupResult = mPreprocessingManager.group(list); 533 534 assertThat(groupResult.size() == 0).isTrue(); 535 } 536 537 @Test addCallStateListener_preCall_triggerChanges()538 public void addCallStateListener_preCall_triggerChanges() { 539 InOrder listenerInOrder = Mockito.inOrder(mCallStateListener1); 540 mPreprocessingManager.addCallStateListener(mCallStateListener1); 541 listenerInOrder.verify(mCallStateListener1).onCallStateChanged(false); 542 543 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 544 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_OFFHOOK); 545 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 546 547 listenerInOrder.verify(mCallStateListener1).onCallStateChanged(true); 548 } 549 550 @Test addCallStateListener_midCall_triggerChanges()551 public void addCallStateListener_midCall_triggerChanges() { 552 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 553 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_OFFHOOK); 554 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 555 556 mPreprocessingManager.addCallStateListener(mCallStateListener1); 557 558 verify(mCallStateListener1).onCallStateChanged(true); 559 } 560 561 @Test addCallStateListener_postCall_triggerChanges()562 public void addCallStateListener_postCall_triggerChanges() { 563 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 564 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_OFFHOOK); 565 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 566 567 intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 568 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_IDLE); 569 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 570 571 mPreprocessingManager.addCallStateListener(mCallStateListener1); 572 573 verify(mCallStateListener1).onCallStateChanged(false); 574 } 575 576 @Test addSameCallListenerTwice_dedupedCorrectly()577 public void addSameCallListenerTwice_dedupedCorrectly() { 578 mPreprocessingManager.addCallStateListener(mCallStateListener1); 579 580 verify(mCallStateListener1).onCallStateChanged(false); 581 mPreprocessingManager.addCallStateListener(mCallStateListener1); 582 583 verify(mCallStateListener1, times(1)).onCallStateChanged(false); 584 } 585 586 @Test removeCallStateListener_midCall_triggerChanges()587 public void removeCallStateListener_midCall_triggerChanges() { 588 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 589 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_OFFHOOK); 590 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 591 592 mPreprocessingManager.addCallStateListener(mCallStateListener1); 593 // Should get triggered with true before calling removeCallStateListener 594 mPreprocessingManager.removeCallStateListener(mCallStateListener1); 595 596 intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 597 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_IDLE); 598 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 599 600 verify(mCallStateListener1, never()).onCallStateChanged(false); 601 } 602 603 @Test multipleCallStateListeners_triggeredAppropriately()604 public void multipleCallStateListeners_triggeredAppropriately() { 605 InOrder listenerInOrder1 = Mockito.inOrder(mCallStateListener1); 606 InOrder listenerInOrder2 = Mockito.inOrder(mCallStateListener2); 607 mPreprocessingManager.addCallStateListener(mCallStateListener1); 608 listenerInOrder1.verify(mCallStateListener1).onCallStateChanged(false); 609 610 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 611 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_OFFHOOK); 612 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 613 614 mPreprocessingManager.addCallStateListener(mCallStateListener2); 615 mPreprocessingManager.removeCallStateListener(mCallStateListener1); 616 617 listenerInOrder1.verify(mCallStateListener1).onCallStateChanged(true); 618 listenerInOrder2.verify(mCallStateListener2).onCallStateChanged(true); 619 620 intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 621 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_IDLE); 622 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 623 624 // only listener 2 should be triggered w/ false 625 listenerInOrder1.verifyNoMoreInteractions(); 626 listenerInOrder2.verify(mCallStateListener2).onCallStateChanged(false); 627 } 628 629 @Test onGroup_removesNotificationGroupWithOnlySummaryNotification()630 public void onGroup_removesNotificationGroupWithOnlySummaryNotification() { 631 List<AlertEntry> list = new ArrayList<>(); 632 list.add(new AlertEntry(mSummaryCStatusBarNotification)); 633 List<NotificationGroup> groupResult = mPreprocessingManager.group(list); 634 635 assertThat(groupResult.isEmpty()).isTrue(); 636 } 637 638 @Test onGroup_splitsNotificationsBySeenAndUnseen()639 public void onGroup_splitsNotificationsBySeenAndUnseen() { 640 List<AlertEntry> list = new ArrayList<>(); 641 list.add(new AlertEntry(mSummaryCStatusBarNotification)); 642 643 List<NotificationGroup> groupResult = mPreprocessingManager.group(list); 644 645 assertThat(groupResult.isEmpty()).isTrue(); 646 } 647 648 @Test onGroup_childNotificationHasTimeStamp_groupHasMostRecentTimeStamp()649 public void onGroup_childNotificationHasTimeStamp_groupHasMostRecentTimeStamp() { 650 mBackgroundNotification.when = 0; 651 mForegroundNotification.when = 1; 652 mNavigationNotification.when = 2; 653 654 mBackgroundNotification.extras.putBoolean(Notification.EXTRA_SHOW_WHEN, true); 655 mForegroundNotification.extras.putBoolean(Notification.EXTRA_SHOW_WHEN, true); 656 mNavigationNotification.extras.putBoolean(Notification.EXTRA_SHOW_WHEN, true); 657 658 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 659 660 groupResult.forEach(group -> { 661 AlertEntry groupSummaryNotification = group.getGroupSummaryNotification(); 662 if (groupSummaryNotification != null 663 && groupSummaryNotification.getNotification() != null) { 664 assertThat(groupSummaryNotification.getNotification() 665 .extras.getBoolean(Notification.EXTRA_SHOW_WHEN)).isTrue(); 666 } 667 }); 668 } 669 670 @Test onRank_ranksNotificationGroups()671 public void onRank_ranksNotificationGroups() { 672 setConfig(/* recentOld= */ true, /* launcherIcon= */ true, /* groupThreshold= */ 2); 673 PreprocessingManager.refreshInstance(); 674 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 675 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 676 List<NotificationGroup> rankResult = mPreprocessingManager.rank(groupResult, mRankingMap); 677 678 // generateRankingMap ranked the notifications in the reverse order. 679 String[] expectedOrder = { 680 GROUP_KEY_C, 681 GROUP_KEY_B, 682 GROUP_KEY_A 683 }; 684 685 for (int i = 0; i < rankResult.size(); i++) { 686 String actualGroupKey = rankResult.get(i).getGroupKey(); 687 String expectedGroupKey = expectedOrder[i]; 688 689 assertThat(actualGroupKey).isEqualTo(expectedGroupKey); 690 } 691 } 692 693 @Test onRank_ranksNotificationsInEachGroup()694 public void onRank_ranksNotificationsInEachGroup() { 695 setConfig(/* recentOld= */true, /* launcherIcon= */ true, /* groupThreshold= */ 2); 696 PreprocessingManager.refreshInstance(); 697 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 698 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 699 List<NotificationGroup> rankResult = mPreprocessingManager.rank(groupResult, mRankingMap); 700 NotificationGroup groupB = rankResult.get(1); 701 702 // first make sure that we have Group B 703 assertThat(groupB.getGroupKey()).isEqualTo(GROUP_KEY_B); 704 705 // generateRankingMap ranked the non-background notifications in the reverse order 706 String[] expectedOrder = { 707 "KEY_NAVIGATION", 708 "KEY_LESS_IMPORTANT_FOREGROUND" 709 }; 710 711 for (int i = 0; i < groupB.getChildNotifications().size(); i++) { 712 String actualKey = groupB.getChildNotifications().get(i).getKey(); 713 String expectedGroupKey = expectedOrder[i]; 714 715 assertThat(actualKey).isEqualTo(expectedGroupKey); 716 } 717 } 718 719 @Test onAdditionalGroupAndRank_isGroupSummary_returnsTheSameGroupsAsStandardGroup()720 public void onAdditionalGroupAndRank_isGroupSummary_returnsTheSameGroupsAsStandardGroup() { 721 Notification additionalNotification = generateNotification(/* isForeground= */ false, 722 /* isNavigation= */ false, /* isGroupSummary= */ true); 723 additionalNotification.category = Notification.CATEGORY_MESSAGE; 724 when(mAdditionalStatusBarNotification.getKey()).thenReturn("ADDITIONAL"); 725 when(mAdditionalStatusBarNotification.getGroupKey()).thenReturn(GROUP_KEY_C); 726 when(mAdditionalStatusBarNotification.getNotification()).thenReturn(additionalNotification); 727 AlertEntry additionalAlertEntry = new AlertEntry(mAdditionalStatusBarNotification); 728 729 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 730 List<AlertEntry> copy = mPreprocessingManager.filter(/* showLessImportantNotifications= */ 731 false, new ArrayList<>(mAlertEntries), mRankingMap); 732 copy.add(additionalAlertEntry); 733 copy.add(new AlertEntry(mSummaryCStatusBarNotification)); 734 List<NotificationGroup> expected = mPreprocessingManager.group(copy); 735 String[] expectedKeys = new String[expected.size()]; 736 for (int i = 0; i < expectedKeys.length; i++) { 737 expectedKeys[i] = expected.get(i).getGroupKey(); 738 } 739 740 List<NotificationGroup> actual = mPreprocessingManager 741 .additionalGroupAndRank(additionalAlertEntry, mRankingMap, /* isUpdate= */ false); 742 743 String[] actualKeys = new String[actual.size()]; 744 for (int i = 0; i < actualKeys.length; i++) { 745 actualKeys[i] = actual.get(i).getGroupKey(); 746 } 747 // We do not care about the order since they are not ranked yet. 748 Arrays.sort(actualKeys); 749 Arrays.sort(expectedKeys); 750 assertThat(actualKeys).isEqualTo(expectedKeys); 751 } 752 753 @Test onAdditionalGroupAndRank_isGroupSummary_maintainsPreviousRanking()754 public void onAdditionalGroupAndRank_isGroupSummary_maintainsPreviousRanking() { 755 Map<String, AlertEntry> testCopy = new HashMap<>(mAlertEntriesMap); 756 // Seed the list with the notifications 757 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 758 759 String key = "NEW_KEY"; 760 String groupKey = "NEW_GROUP_KEY"; 761 Notification newNotification = generateNotification(/* isForeground= */ false, 762 /* isNavigation= */ false, /* isGroupSummary= */ true); 763 StatusBarNotification newSbn = mock(StatusBarNotification.class); 764 when(newSbn.getNotification()).thenReturn(newNotification); 765 when(newSbn.getKey()).thenReturn(key); 766 when(newSbn.getGroupKey()).thenReturn(groupKey); 767 768 AlertEntry newEntry = new AlertEntry(newSbn); 769 770 // Change the ordering, add a new notification and validate that the existing 771 // notifications don't reorder 772 AlertEntry first = mAlertEntries.get(0); 773 mAlertEntries.remove(0); 774 mAlertEntries.add(first); 775 776 List<NotificationGroup> additionalRanked = mPreprocessingManager.additionalGroupAndRank( 777 newEntry, generateRankingMap(mAlertEntries), /* isUpdate= */ false) 778 .stream() 779 .filter(g -> !g.getGroupKey().equals(groupKey)) 780 .collect(Collectors.toList()); 781 782 List<NotificationGroup> standardRanked = mPreprocessingManager.rank( 783 mPreprocessingManager.process(/* showLessImportantNotifications = */ false, 784 testCopy, mRankingMap), mRankingMap); 785 786 assertThat(additionalRanked.size()).isEqualTo(standardRanked.size()); 787 788 for (int i = 0; i < additionalRanked.size(); i++) { 789 assertThat(additionalRanked.get(i).getGroupKey()).isEqualTo( 790 standardRanked.get(i).getGroupKey()); 791 } 792 } 793 794 @Test onAdditionalGroupAndRank_isGroupSummary_prependsHighRankNotification()795 public void onAdditionalGroupAndRank_isGroupSummary_prependsHighRankNotification() { 796 // Seed the list 797 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 798 799 String key = "NEW_KEY"; 800 String groupKey = "NEW_GROUP_KEY"; 801 Notification newNotification = generateNotification(/* isForeground= */ false, 802 /* isNavigation= */ false, /* isGroupSummary= */ true); 803 StatusBarNotification newSbn = mock(StatusBarNotification.class); 804 when(newSbn.getNotification()).thenReturn(newNotification); 805 when(newSbn.getKey()).thenReturn(key); 806 when(newSbn.getGroupKey()).thenReturn(groupKey); 807 808 AlertEntry newEntry = new AlertEntry(newSbn); 809 mAlertEntries.add(newEntry); 810 811 List<NotificationGroup> result = mPreprocessingManager.additionalGroupAndRank(newEntry, 812 generateRankingMap(mAlertEntries), /* isUpdate= */ false); 813 assertThat(result.get(0).getSingleNotification()).isEqualTo(newEntry); 814 } 815 816 @Test onAdditionalGroupAndRank_notGroupSummary_isUpdate_notificationUpdated()817 public void onAdditionalGroupAndRank_notGroupSummary_isUpdate_notificationUpdated() { 818 when(mNotificationDataManager.isNotificationSeen(mImportantForeground)).thenReturn(false); 819 // Seed the list 820 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 821 String key = mImportantForeground.getKey(); 822 String groupKey = mImportantForeground.getStatusBarNotification().getGroupKey(); 823 Notification newNotification = generateNotification(/* isForeground= */ true, 824 /* isNavigation= */ false, /* isGroupSummary= */ false); 825 StatusBarNotification newSbn = mock(StatusBarNotification.class); 826 when(newSbn.getNotification()).thenReturn(newNotification); 827 when(newSbn.getKey()).thenReturn(key); 828 when(newSbn.getGroupKey()).thenReturn(groupKey); 829 when(newSbn.getId()).thenReturn(123); 830 AlertEntry newEntry = new AlertEntry(newSbn); 831 832 List<NotificationGroup> result = mPreprocessingManager.additionalGroupAndRank(newEntry, 833 generateRankingMap(mAlertEntries), /* isUpdate= */ true); 834 835 assertThat(result.get(0).getSingleNotification().getStatusBarNotification().getId()) 836 .isEqualTo(123); 837 } 838 839 @Test onAdditionalGroupAndRank_updateToNotificationInSeenGroup_newUnseenGroupCreated()840 public void onAdditionalGroupAndRank_updateToNotificationInSeenGroup_newUnseenGroupCreated() { 841 when(mStatusBarNotification6.getGroupKey()).thenReturn(GROUP_KEY_C); 842 when(mStatusBarNotification7.getGroupKey()).thenReturn(GROUP_KEY_C); 843 when(mStatusBarNotification8.getGroupKey()).thenReturn(GROUP_KEY_C); 844 when(mStatusBarNotification9.getGroupKey()).thenReturn(GROUP_KEY_C); 845 when(mStatusBarNotification10.getGroupKey()).thenReturn(GROUP_KEY_C); 846 when(mStatusBarNotification11.getGroupKey()).thenReturn(GROUP_KEY_C); 847 when(mStatusBarNotification12.getGroupKey()).thenReturn(GROUP_KEY_C); 848 initTestData(/* includeAdditionalNotifs= */ true); 849 when(mNotificationDataManager.isNotificationSeen(mImportantForeground)).thenReturn(true); 850 when(mNotificationDataManager.isNotificationSeen(mImportantForeground2)).thenReturn(true); 851 when(mNotificationDataManager.isNotificationSeen(mImportantForeground3)).thenReturn(true); 852 when(mNotificationDataManager.isNotificationSeen(mImportantForeground4)).thenReturn(true); 853 when(mNotificationDataManager.isNotificationSeen(mImportantForeground5)).thenReturn(true); 854 when(mNotificationDataManager.isNotificationSeen(mImportantForeground6)).thenReturn(true); 855 when(mNotificationDataManager.isNotificationSeen(mImportantForeground7)).thenReturn(true); 856 PreprocessingManager.refreshInstance(); 857 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 858 mPreprocessingManager.setNotificationDataManager(mNotificationDataManager); 859 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 860 List<NotificationGroup> processedGroupsWithGroupKeyC = getGroupsWithGroupKey(GROUP_KEY_C, 861 mPreprocessingManager.getOldProcessedNotifications()); 862 // assert notifications with GROUP_KEY_C are grouped into one seen NotificationGroup. 863 assertThat(processedGroupsWithGroupKeyC).hasSize(1); 864 assertThat(processedGroupsWithGroupKeyC.get(0).isSeen()).isTrue(); 865 // Create a notification with same key and group key to be sent as an update 866 String key = mImportantForeground.getKey(); 867 Notification newNotification = generateNotification(/* isForeground= */ true, 868 /* isNavigation= */ false, /* isGroupSummary= */ false); 869 StatusBarNotification newSbn = mock(StatusBarNotification.class); 870 when(newSbn.getNotification()).thenReturn(newNotification); 871 when(newSbn.getKey()).thenReturn(key); 872 when(newSbn.getGroupKey()).thenReturn(GROUP_KEY_C); 873 when(newSbn.getId()).thenReturn(123); 874 AlertEntry newEntry = new AlertEntry(newSbn); 875 876 List<NotificationGroup> result = mPreprocessingManager.additionalGroupAndRank(newEntry, 877 generateRankingMap(mAlertEntries), /* isUpdate= */ true); 878 879 List<NotificationGroup> unSeenGroupsWithGroupKeyC = getGroupsWithSeenState( 880 /* isSeen= */ false, getGroupsWithGroupKey(GROUP_KEY_C, result)); 881 assertThat(unSeenGroupsWithGroupKeyC).hasSize(1); 882 assertThat(unSeenGroupsWithGroupKeyC.get(0).getSingleNotification()).isEqualTo(newEntry); 883 } 884 885 @Test onAdditionalGroupAndRank_updateToNotificationInSeenGroup_oldGroupNotDeleted()886 public void onAdditionalGroupAndRank_updateToNotificationInSeenGroup_oldGroupNotDeleted() { 887 // If the old group size is more than zero, it should not be deleted 888 when(mStatusBarNotification6.getGroupKey()).thenReturn(GROUP_KEY_C); 889 when(mStatusBarNotification7.getGroupKey()).thenReturn(GROUP_KEY_C); 890 when(mStatusBarNotification8.getGroupKey()).thenReturn(GROUP_KEY_C); 891 when(mStatusBarNotification9.getGroupKey()).thenReturn(GROUP_KEY_C); 892 when(mStatusBarNotification10.getGroupKey()).thenReturn(GROUP_KEY_C); 893 when(mStatusBarNotification11.getGroupKey()).thenReturn(GROUP_KEY_C); 894 when(mStatusBarNotification12.getGroupKey()).thenReturn(GROUP_KEY_C); 895 initTestData(/* includeAdditionalNotifs= */ true); 896 when(mNotificationDataManager.isNotificationSeen(mImportantForeground)).thenReturn(true); 897 when(mNotificationDataManager.isNotificationSeen(mImportantForeground2)).thenReturn(true); 898 when(mNotificationDataManager.isNotificationSeen(mImportantForeground3)).thenReturn(true); 899 when(mNotificationDataManager.isNotificationSeen(mImportantForeground4)).thenReturn(true); 900 when(mNotificationDataManager.isNotificationSeen(mImportantForeground5)).thenReturn(true); 901 when(mNotificationDataManager.isNotificationSeen(mImportantForeground6)).thenReturn(true); 902 when(mNotificationDataManager.isNotificationSeen(mImportantForeground7)).thenReturn(true); 903 PreprocessingManager.refreshInstance(); 904 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 905 mPreprocessingManager.setNotificationDataManager(mNotificationDataManager); 906 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 907 List<NotificationGroup> processedGroupsWithGroupKeyC = getGroupsWithGroupKey(GROUP_KEY_C, 908 mPreprocessingManager.getOldProcessedNotifications()); 909 assertThat(processedGroupsWithGroupKeyC).hasSize(1); 910 assertThat(processedGroupsWithGroupKeyC.get(0).getChildNotifications()).hasSize(7); 911 // Create a notification with same key and group key to be sent as an update 912 String key = mImportantForeground.getKey(); 913 Notification newNotification = generateNotification(/* isForeground= */ true, 914 /* isNavigation= */ false, /* isGroupSummary= */ false); 915 StatusBarNotification newSbn = mock(StatusBarNotification.class); 916 when(newSbn.getNotification()).thenReturn(newNotification); 917 when(newSbn.getKey()).thenReturn(key); 918 when(newSbn.getGroupKey()).thenReturn(GROUP_KEY_C); 919 when(newSbn.getId()).thenReturn(123); 920 AlertEntry newEntry = new AlertEntry(newSbn); 921 922 List<NotificationGroup> result = mPreprocessingManager.additionalGroupAndRank(newEntry, 923 generateRankingMap(mAlertEntries), /* isUpdate= */ true); 924 925 List<NotificationGroup> seenGroupsWithGroupKeyC = getGroupsWithSeenState( 926 /* isSeen= */ true, getGroupsWithGroupKey(GROUP_KEY_C, result)); 927 assertThat(seenGroupsWithGroupKeyC).hasSize(1); 928 assertThat(seenGroupsWithGroupKeyC.get(0).getChildNotifications()).hasSize(6); 929 } 930 931 @Test onAdditionalGroupAndRank_updateToNotificationInSeenGroup_oldGroupDeleted()932 public void onAdditionalGroupAndRank_updateToNotificationInSeenGroup_oldGroupDeleted() { 933 // If the old group size is zero, it should not be deleted 934 when(mNotificationDataManager.isNotificationSeen(mImportantForeground)).thenReturn(true); 935 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 936 List<NotificationGroup> processedGroupsWithGroupKeyC = getGroupsWithGroupKey(GROUP_KEY_C, 937 mPreprocessingManager.getOldProcessedNotifications()); 938 assertThat(processedGroupsWithGroupKeyC).hasSize(1); 939 assertThat(processedGroupsWithGroupKeyC.get(0).getChildNotifications()).hasSize(1); 940 // Create a notification with same key and group key to be sent as an update 941 String key = mImportantForeground.getKey(); 942 Notification newNotification = generateNotification(/* isForeground= */ true, 943 /* isNavigation= */ false, /* isGroupSummary= */ false); 944 StatusBarNotification newSbn = mock(StatusBarNotification.class); 945 when(newSbn.getNotification()).thenReturn(newNotification); 946 when(newSbn.getKey()).thenReturn(key); 947 when(newSbn.getGroupKey()).thenReturn(GROUP_KEY_C); 948 when(newSbn.getId()).thenReturn(123); 949 AlertEntry newEntry = new AlertEntry(newSbn); 950 951 List<NotificationGroup> result = mPreprocessingManager.additionalGroupAndRank(newEntry, 952 generateRankingMap(mAlertEntries), /* isUpdate= */ true); 953 954 List<NotificationGroup> seenGroupsWithGroupKeyC = getGroupsWithSeenState( 955 /* isSeen= */ true, getGroupsWithGroupKey(GROUP_KEY_C, result)); 956 assertThat(seenGroupsWithGroupKeyC).hasSize(0); 957 } 958 959 @Test onAdditionalGroupAndRank_newNotification_setAsSeenInDataManger()960 public void onAdditionalGroupAndRank_newNotification_setAsSeenInDataManger() { 961 String key = "TEST_KEY"; 962 mPreprocessingManager.setNotificationDataManager(mNotificationDataManager); 963 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 964 Notification newNotification = generateNotification(/* isForeground= */ false, 965 /* isNavigation= */ false, /* isGroupSummary= */ false); 966 StatusBarNotification newSbn = mock(StatusBarNotification.class); 967 when(newSbn.getNotification()).thenReturn(newNotification); 968 when(newSbn.getKey()).thenReturn(key); 969 when(newSbn.getGroupKey()).thenReturn("groupKey"); 970 AlertEntry newEntry = new AlertEntry(newSbn); 971 972 mPreprocessingManager.additionalGroupAndRank(newEntry, 973 generateRankingMap(mAlertEntries), /* isUpdate= */ false); 974 975 ArgumentCaptor<AlertEntry> arg = ArgumentCaptor.forClass(AlertEntry.class); 976 verify(mNotificationDataManager).setNotificationAsSeen(arg.capture()); 977 assertThat(arg.getValue().getKey()).isEqualTo(key); 978 } 979 980 @Test onAdditionalGroupAndRank_addToExistingGroup_groupSurpassGroupingThresholdExist()981 public void onAdditionalGroupAndRank_addToExistingGroup_groupSurpassGroupingThresholdExist() { 982 String key = "TEST_KEY"; 983 String groupKey = "TEST_GROUP_KEY"; 984 int numberOfGroupNotifications = 5; 985 mContext.getOrCreateTestableResources().addOverride( 986 R.integer.config_minimumGroupingThreshold, /* value= */ 4); 987 generateNotificationsWithSameGroupKey(numberOfGroupNotifications, groupKey); 988 generateGroupSummaryNotification(groupKey); 989 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 990 Notification newNotification = generateNotification(/* isForeground= */ false, 991 /* isNavigation= */ false, /* isGroupSummary= */ false); 992 StatusBarNotification newSbn = mock(StatusBarNotification.class); 993 when(newSbn.getNotification()).thenReturn(newNotification); 994 when(newSbn.getKey()).thenReturn(key); 995 when(newSbn.getGroupKey()).thenReturn(groupKey); 996 AlertEntry newEntry = new AlertEntry(newSbn); 997 998 List<NotificationGroup> rawResult = mPreprocessingManager.additionalGroupAndRank(newEntry, 999 generateRankingMap(mAlertEntries), /* isUpdate= */ false); 1000 1001 List<NotificationGroup> resultNotificationGroups = rawResult.stream() 1002 .filter(ng -> TextUtils.equals(ng.getGroupKey(), groupKey)) 1003 .collect(Collectors.toList()); 1004 assertThat(resultNotificationGroups.size()).isEqualTo(1); 1005 List<AlertEntry> resultAlertEntries = resultNotificationGroups.get(0) 1006 .getChildNotifications(); 1007 assertThat(resultAlertEntries.size()).isEqualTo(numberOfGroupNotifications + 1); 1008 assertThat(resultAlertEntries.get(resultAlertEntries.size() - 1).getKey()).isEqualTo(key); 1009 } 1010 1011 @Test onAdditionalGroupAndRank_addNewNotification_notSurpassGroupingThreshold()1012 public void onAdditionalGroupAndRank_addNewNotification_notSurpassGroupingThreshold() { 1013 String key = "TEST_KEY"; 1014 String groupKey = "TEST_GROUP_KEY"; 1015 int numberOfGroupNotifications = 2; 1016 mContext.getOrCreateTestableResources().addOverride( 1017 R.integer.config_minimumGroupingThreshold, /* value= */ 4); 1018 generateNotificationsWithSameGroupKey(numberOfGroupNotifications, groupKey); 1019 generateGroupSummaryNotification(groupKey); 1020 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 1021 Notification newNotification = generateNotification(/* isForeground= */ false, 1022 /* isNavigation= */ false, /* isGroupSummary= */ false); 1023 StatusBarNotification newSbn = mock(StatusBarNotification.class); 1024 when(newSbn.getNotification()).thenReturn(newNotification); 1025 when(newSbn.getKey()).thenReturn(key); 1026 when(newSbn.getGroupKey()).thenReturn(groupKey); 1027 AlertEntry newEntry = new AlertEntry(newSbn); 1028 1029 List<NotificationGroup> rawResult = mPreprocessingManager.additionalGroupAndRank(newEntry, 1030 generateRankingMap(mAlertEntries), /* isUpdate= */ false); 1031 1032 List<NotificationGroup> resultNotificationGroups = rawResult.stream() 1033 .filter(ng -> TextUtils.equals(ng.getGroupKey(), groupKey)) 1034 .collect(Collectors.toList()); 1035 assertThat(resultNotificationGroups.size()).isEqualTo(numberOfGroupNotifications + 1); 1036 } 1037 1038 @Test onAdditionalGroupAndRank_createsNewGroup_surpassGroupingThreshold()1039 public void onAdditionalGroupAndRank_createsNewGroup_surpassGroupingThreshold() { 1040 String key = "TEST_KEY"; 1041 String groupKey = "TEST_GROUP_KEY"; 1042 int numberOfGroupNotifications = 3; 1043 mContext.getOrCreateTestableResources().addOverride( 1044 R.integer.config_minimumGroupingThreshold, /* value= */ 4); 1045 generateNotificationsWithSameGroupKey(numberOfGroupNotifications, groupKey); 1046 generateGroupSummaryNotification(groupKey); 1047 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 1048 Notification newNotification = generateNotification(/* isForeground= */ false, 1049 /* isNavigation= */ false, /* isGroupSummary= */ false); 1050 StatusBarNotification newSbn = mock(StatusBarNotification.class); 1051 when(newSbn.getNotification()).thenReturn(newNotification); 1052 when(newSbn.getKey()).thenReturn(key); 1053 when(newSbn.getGroupKey()).thenReturn(groupKey); 1054 AlertEntry newEntry = new AlertEntry(newSbn); 1055 1056 List<NotificationGroup> rawResult = mPreprocessingManager.additionalGroupAndRank(newEntry, 1057 generateRankingMap(mAlertEntries), /* isUpdate= */ false); 1058 1059 List<NotificationGroup> resultNotificationGroups = rawResult.stream() 1060 .filter(ng -> TextUtils.equals(ng.getGroupKey(), groupKey)) 1061 .collect(Collectors.toList()); 1062 assertThat(resultNotificationGroups.size()).isEqualTo(1); 1063 assertThat(resultNotificationGroups.get(0).getChildCount()) 1064 .isEqualTo(numberOfGroupNotifications + 1); 1065 } 1066 1067 @Test onAdditionalGroupAndRank_doesNotGroup_groupSummaryMissing()1068 public void onAdditionalGroupAndRank_doesNotGroup_groupSummaryMissing() { 1069 String key = "TEST_KEY"; 1070 String groupKey = "TEST_GROUP_KEY"; 1071 int numberOfGroupNotifications = 3; 1072 mContext.getOrCreateTestableResources().addOverride( 1073 R.integer.config_minimumGroupingThreshold, /* value= */ 4); 1074 generateNotificationsWithSameGroupKey(numberOfGroupNotifications, groupKey); 1075 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 1076 Notification newNotification = generateNotification(/* isForeground= */ false, 1077 /* isNavigation= */ false, /* isGroupSummary= */ false); 1078 StatusBarNotification newSbn = mock(StatusBarNotification.class); 1079 when(newSbn.getNotification()).thenReturn(newNotification); 1080 when(newSbn.getKey()).thenReturn(key); 1081 when(newSbn.getGroupKey()).thenReturn(groupKey); 1082 AlertEntry newEntry = new AlertEntry(newSbn); 1083 1084 List<NotificationGroup> rawResult = mPreprocessingManager.additionalGroupAndRank(newEntry, 1085 generateRankingMap(mAlertEntries), /* isUpdate= */ false); 1086 1087 List<NotificationGroup> resultNotificationGroups = rawResult.stream() 1088 .filter(ng -> TextUtils.equals(ng.getGroupKey(), groupKey)) 1089 .collect(Collectors.toList()); 1090 assertThat(resultNotificationGroups.size()).isEqualTo(numberOfGroupNotifications + 1); 1091 } 1092 1093 @Test onUpdateNotifications_notificationRemoved_removesNotification()1094 public void onUpdateNotifications_notificationRemoved_removesNotification() { 1095 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 1096 1097 List<NotificationGroup> newList = 1098 mPreprocessingManager.updateNotifications( 1099 /* showLessImportantNotifications= */ false, 1100 mImportantForeground, 1101 CarNotificationListener.NOTIFY_NOTIFICATION_REMOVED, 1102 mRankingMap); 1103 1104 assertThat(mPreprocessingManager.getOldNotifications().containsKey( 1105 mImportantForeground.getKey())).isFalse(); 1106 } 1107 1108 @Test onUpdateNotification_notificationPosted_isUpdate_putsNotification()1109 public void onUpdateNotification_notificationPosted_isUpdate_putsNotification() { 1110 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 1111 int beforeSize = mPreprocessingManager.getOldNotifications().size(); 1112 Notification newNotification = new Notification.Builder(mContext, CHANNEL_ID) 1113 .setContentTitle("NEW_TITLE") 1114 .setGroup(OVERRIDE_GROUP_KEY) 1115 .setGroupSummary(false) 1116 .build(); 1117 newNotification.category = Notification.CATEGORY_NAVIGATION; 1118 when(mImportantForeground.getStatusBarNotification().getNotification()) 1119 .thenReturn(newNotification); 1120 List<NotificationGroup> newList = 1121 mPreprocessingManager.updateNotifications( 1122 /* showLessImportantNotifications= */ false, 1123 mImportantForeground, 1124 CarNotificationListener.NOTIFY_NOTIFICATION_POSTED, 1125 mRankingMap); 1126 1127 int afterSize = mPreprocessingManager.getOldNotifications().size(); 1128 AlertEntry updated = (AlertEntry) mPreprocessingManager.getOldNotifications().get( 1129 mImportantForeground.getKey()); 1130 assertThat(updated).isNotNull(); 1131 assertThat(updated.getNotification().category).isEqualTo(Notification.CATEGORY_NAVIGATION); 1132 assertThat(afterSize).isEqualTo(beforeSize); 1133 } 1134 1135 @Test onUpdateNotification_notificationPosted_isNotUpdate_addsNotification()1136 public void onUpdateNotification_notificationPosted_isNotUpdate_addsNotification() { 1137 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 1138 int beforeSize = mPreprocessingManager.getOldNotifications().size(); 1139 Notification additionalNotification = generateNotification(/* isForeground= */ true, 1140 /* isNavigation= */ false, /* isGroupSummary= */ true); 1141 additionalNotification.category = Notification.CATEGORY_MESSAGE; 1142 when(mAdditionalStatusBarNotification.getKey()).thenReturn("ADDITIONAL"); 1143 when(mAdditionalStatusBarNotification.getGroupKey()).thenReturn(GROUP_KEY_C); 1144 when(mAdditionalStatusBarNotification.getNotification()).thenReturn(additionalNotification); 1145 AlertEntry additionalAlertEntry = new AlertEntry(mAdditionalStatusBarNotification); 1146 1147 List<NotificationGroup> newList = 1148 mPreprocessingManager.updateNotifications( 1149 /* showLessImportantNotifications= */ false, 1150 additionalAlertEntry, 1151 CarNotificationListener.NOTIFY_NOTIFICATION_POSTED, 1152 mRankingMap); 1153 1154 int afterSize = mPreprocessingManager.getOldNotifications().size(); 1155 AlertEntry posted = (AlertEntry) mPreprocessingManager.getOldNotifications().get( 1156 additionalAlertEntry.getKey()); 1157 assertThat(posted).isNotNull(); 1158 assertThat(posted.getKey()).isEqualTo("ADDITIONAL"); 1159 assertThat(afterSize).isEqualTo(beforeSize + 1); 1160 } 1161 setConfig(boolean recentOld, boolean launcherIcon, int groupThreshold)1162 private void setConfig(boolean recentOld, boolean launcherIcon, int groupThreshold) { 1163 TestableResources testableResources = mContext.getOrCreateTestableResources(); 1164 testableResources.removeOverride(R.bool.config_showRecentAndOldHeaders); 1165 testableResources.removeOverride(R.bool.config_useLauncherIcon); 1166 testableResources.removeOverride(R.integer.config_minimumGroupingThreshold); 1167 testableResources.addOverride(R.bool.config_showRecentAndOldHeaders, recentOld); 1168 testableResources.addOverride(R.bool.config_useLauncherIcon, launcherIcon); 1169 testableResources.addOverride(R.integer.config_minimumGroupingThreshold, groupThreshold); 1170 } 1171 1172 /** 1173 * Wraps StatusBarNotifications with AlertEntries and generates AlertEntriesMap and 1174 * RankingsMap. 1175 */ initTestData(boolean includeAdditionalNotifs)1176 private void initTestData(boolean includeAdditionalNotifs) { 1177 mAlertEntries = new ArrayList<>(); 1178 mLessImportantBackground = new AlertEntry(mStatusBarNotification1); 1179 mLessImportantForeground = new AlertEntry(mStatusBarNotification2); 1180 mMedia = new AlertEntry(mStatusBarNotification3); 1181 mNavigation = new AlertEntry(mStatusBarNotification4); 1182 mImportantBackground = new AlertEntry(mStatusBarNotification5); 1183 mImportantForeground = new AlertEntry(mStatusBarNotification6); 1184 if (includeAdditionalNotifs) { 1185 mImportantForeground2 = new AlertEntry(mStatusBarNotification7); 1186 mImportantForeground3 = new AlertEntry(mStatusBarNotification8); 1187 mImportantForeground4 = new AlertEntry(mStatusBarNotification9); 1188 mImportantForeground5 = new AlertEntry(mStatusBarNotification10); 1189 mImportantForeground6 = new AlertEntry(mStatusBarNotification11); 1190 mImportantForeground7 = new AlertEntry(mStatusBarNotification12); 1191 } 1192 mAlertEntries.add(mLessImportantBackground); 1193 mAlertEntries.add(mLessImportantForeground); 1194 mAlertEntries.add(mMedia); 1195 mAlertEntries.add(mNavigation); 1196 mAlertEntries.add(mImportantBackground); 1197 mAlertEntries.add(mImportantForeground); 1198 if (includeAdditionalNotifs) { 1199 mAlertEntries.add(mImportantForeground2); 1200 mAlertEntries.add(mImportantForeground3); 1201 mAlertEntries.add(mImportantForeground4); 1202 mAlertEntries.add(mImportantForeground5); 1203 mAlertEntries.add(mImportantForeground6); 1204 mAlertEntries.add(mImportantForeground7); 1205 } 1206 mAlertEntriesMap = new HashMap<>(); 1207 mAlertEntriesMap.put(mLessImportantBackground.getKey(), mLessImportantBackground); 1208 mAlertEntriesMap.put(mLessImportantForeground.getKey(), mLessImportantForeground); 1209 mAlertEntriesMap.put(mMedia.getKey(), mMedia); 1210 mAlertEntriesMap.put(mNavigation.getKey(), mNavigation); 1211 mAlertEntriesMap.put(mImportantBackground.getKey(), mImportantBackground); 1212 mAlertEntriesMap.put(mImportantForeground.getKey(), mImportantForeground); 1213 if (includeAdditionalNotifs) { 1214 mAlertEntriesMap.put(mImportantForeground2.getKey(), mImportantForeground2); 1215 mAlertEntriesMap.put(mImportantForeground3.getKey(), mImportantForeground3); 1216 mAlertEntriesMap.put(mImportantForeground4.getKey(), mImportantForeground4); 1217 mAlertEntriesMap.put(mImportantForeground5.getKey(), mImportantForeground5); 1218 mAlertEntriesMap.put(mImportantForeground6.getKey(), mImportantForeground6); 1219 mAlertEntriesMap.put(mImportantForeground7.getKey(), mImportantForeground7); 1220 } 1221 mRankingMap = generateRankingMap(mAlertEntries); 1222 } 1223 getEmptyAutoGeneratedGroupSummary()1224 private AlertEntry getEmptyAutoGeneratedGroupSummary() { 1225 Notification notification = new Notification.Builder(mContext, CHANNEL_ID) 1226 .setContentTitle(CONTENT_TITLE) 1227 .setSmallIcon(android.R.drawable.sym_def_app_icon) 1228 .setGroup(OVERRIDE_GROUP_KEY) 1229 .setGroupSummary(true) 1230 .build(); 1231 StatusBarNotification statusBarNotification = new StatusBarNotification( 1232 PKG, OP_PKG, ID, TAG, UID, INITIAL_PID, notification, USER_HANDLE, 1233 OVERRIDE_GROUP_KEY, POST_TIME); 1234 statusBarNotification.setOverrideGroupKey(OVERRIDE_GROUP_KEY); 1235 1236 return new AlertEntry(statusBarNotification); 1237 } 1238 generateNotification(boolean isForeground, boolean isNavigation, boolean isGroupSummary)1239 private Notification generateNotification(boolean isForeground, boolean isNavigation, 1240 boolean isGroupSummary) { 1241 Notification notification = new Notification.Builder(mContext, CHANNEL_ID) 1242 .setContentTitle(CONTENT_TITLE) 1243 .setSmallIcon(android.R.drawable.sym_def_app_icon) 1244 .setGroup(OVERRIDE_GROUP_KEY) 1245 .setGroupSummary(isGroupSummary) 1246 .build(); 1247 1248 if (isForeground) { 1249 // this will reset flags previously set like FLAG_GROUP_SUMMARY 1250 notification.flags = Notification.FLAG_FOREGROUND_SERVICE; 1251 } 1252 1253 if (isNavigation) { 1254 notification.category = Notification.CATEGORY_NAVIGATION; 1255 } 1256 return notification; 1257 } 1258 generateStringOfLength(int length)1259 private String generateStringOfLength(int length) { 1260 String string = ""; 1261 for (int i = 0; i < length; i++) { 1262 string += "*"; 1263 } 1264 1265 return string; 1266 } 1267 1268 /** 1269 * Ranks the provided alertEntries in reverse order. 1270 * 1271 * All methods that follow afterwards help assigning diverse attributes to the {@link 1272 * android.service.notification.NotificationListenerService.Ranking} instances. 1273 */ generateRankingMap( List<AlertEntry> alertEntries)1274 private NotificationListenerService.RankingMap generateRankingMap( 1275 List<AlertEntry> alertEntries) { 1276 NotificationListenerService.Ranking[] rankings = 1277 new NotificationListenerService.Ranking[alertEntries.size()]; 1278 for (int i = 0; i < alertEntries.size(); i++) { 1279 String key = alertEntries.get(i).getKey(); 1280 int rank = alertEntries.size() - i; // ranking in reverse order; 1281 NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); 1282 ranking.populate( 1283 key, 1284 rank, 1285 !isIntercepted(i), 1286 getVisibilityOverride(i), 1287 getSuppressedVisualEffects(i), 1288 getImportance(i), 1289 getExplanation(key), 1290 getOverrideGroupKey(key), 1291 getChannel(key, i), 1292 getPeople(key, i), 1293 getSnoozeCriteria(key, i), 1294 getShowBadge(i), 1295 getUserSentiment(i), 1296 getHidden(i), 1297 lastAudiblyAlerted(i), 1298 getNoisy(i), 1299 getSmartActions(key, i), 1300 getSmartReplies(key, i), 1301 canBubble(i), 1302 isVisuallyInterruptive(i), 1303 isConversation(i), 1304 /* shortcutInfo= */ null, 1305 getRankingAdjustment(i), 1306 isBubble(i), 1307 /* proposedImportance= */ 0, 1308 /* sensitiveContent= */ false 1309 ); 1310 rankings[i] = ranking; 1311 } 1312 1313 NotificationListenerService.RankingMap rankingMap 1314 = new NotificationListenerService.RankingMap(rankings); 1315 1316 return rankingMap; 1317 } 1318 generateNotificationsWithSameGroupKey(int numberOfNotifications, String groupKey)1319 private void generateNotificationsWithSameGroupKey(int numberOfNotifications, String groupKey) { 1320 for (int i = 0; i < numberOfNotifications; i++) { 1321 String key = "BASE_KEY_" + i; 1322 Notification notification = generateNotification(/* isForeground= */ false, 1323 /* isNavigation= */ false, /* isGroupSummary= */ false); 1324 StatusBarNotification sbn = mock(StatusBarNotification.class); 1325 when(sbn.getNotification()).thenReturn(notification); 1326 when(sbn.getKey()).thenReturn(key); 1327 when(sbn.getGroupKey()).thenReturn(groupKey); 1328 AlertEntry alertEntry = new AlertEntry(sbn); 1329 mAlertEntries.add(alertEntry); 1330 mAlertEntriesMap.put(alertEntry.getKey(), alertEntry); 1331 } 1332 } 1333 generateGroupSummaryNotification(String groupKey)1334 private void generateGroupSummaryNotification(String groupKey) { 1335 Notification groupSummary = generateNotification(/* isForeground= */ false, 1336 /* isNavigation= */ false, /* isGroupSummary= */ true); 1337 StatusBarNotification sbn = mock(StatusBarNotification.class); 1338 when(sbn.getNotification()).thenReturn(groupSummary); 1339 when(sbn.getKey()).thenReturn("KEY_GROUP_SUMMARY"); 1340 when(sbn.getGroupKey()).thenReturn(groupKey); 1341 AlertEntry alertEntry = new AlertEntry(sbn); 1342 mAlertEntries.add(alertEntry); 1343 mAlertEntriesMap.put(alertEntry.getKey(), alertEntry); 1344 } 1345 getVisibilityOverride(int index)1346 private int getVisibilityOverride(int index) { 1347 return index * 9; 1348 } 1349 getOverrideGroupKey(String key)1350 private String getOverrideGroupKey(String key) { 1351 return key + key; 1352 } 1353 isIntercepted(int index)1354 private boolean isIntercepted(int index) { 1355 return index % 2 == 0; 1356 } 1357 getSuppressedVisualEffects(int index)1358 private int getSuppressedVisualEffects(int index) { 1359 return index * 2; 1360 } 1361 getImportance(int index)1362 private int getImportance(int index) { 1363 return index; 1364 } 1365 getExplanation(String key)1366 private String getExplanation(String key) { 1367 return key + "explain"; 1368 } 1369 getChannel(String key, int index)1370 private NotificationChannel getChannel(String key, int index) { 1371 return new NotificationChannel(key, key, getImportance(index)); 1372 } 1373 getShowBadge(int index)1374 private boolean getShowBadge(int index) { 1375 return index % 3 == 0; 1376 } 1377 getUserSentiment(int index)1378 private int getUserSentiment(int index) { 1379 switch (index % 3) { 1380 case 0: 1381 return USER_SENTIMENT_NEGATIVE; 1382 case 1: 1383 return USER_SENTIMENT_NEUTRAL; 1384 case 2: 1385 return USER_SENTIMENT_POSITIVE; 1386 } 1387 return USER_SENTIMENT_NEUTRAL; 1388 } 1389 getHidden(int index)1390 private boolean getHidden(int index) { 1391 return index % 2 == 0; 1392 } 1393 lastAudiblyAlerted(int index)1394 private long lastAudiblyAlerted(int index) { 1395 return index * 2000; 1396 } 1397 getNoisy(int index)1398 private boolean getNoisy(int index) { 1399 return index < 1; 1400 } 1401 getPeople(String key, int index)1402 private ArrayList<String> getPeople(String key, int index) { 1403 ArrayList<String> people = new ArrayList<>(); 1404 for (int i = 0; i < index; i++) { 1405 people.add(i + key); 1406 } 1407 return people; 1408 } 1409 getSnoozeCriteria(String key, int index)1410 private ArrayList<SnoozeCriterion> getSnoozeCriteria(String key, int index) { 1411 ArrayList<SnoozeCriterion> snooze = new ArrayList<>(); 1412 for (int i = 0; i < index; i++) { 1413 snooze.add(new SnoozeCriterion(key + i, getExplanation(key), key)); 1414 } 1415 return snooze; 1416 } 1417 getSmartActions(String key, int index)1418 private ArrayList<Notification.Action> getSmartActions(String key, int index) { 1419 ArrayList<Notification.Action> actions = new ArrayList<>(); 1420 for (int i = 0; i < index; i++) { 1421 PendingIntent intent = PendingIntent.getBroadcast( 1422 mContext, 1423 index /*requestCode*/, 1424 new Intent("ACTION_" + key), 1425 FLAG_IMMUTABLE); 1426 actions.add(new Notification.Action.Builder(null /*icon*/, key, intent).build()); 1427 } 1428 return actions; 1429 } 1430 getSmartReplies(String key, int index)1431 private ArrayList<CharSequence> getSmartReplies(String key, int index) { 1432 ArrayList<CharSequence> choices = new ArrayList<>(); 1433 for (int i = 0; i < index; i++) { 1434 choices.add("choice_" + key + "_" + i); 1435 } 1436 return choices; 1437 } 1438 getGroupsWithGroupKey(String groupKey, List<NotificationGroup> notificationGroups)1439 private List<NotificationGroup> getGroupsWithGroupKey(String groupKey, 1440 List<NotificationGroup> notificationGroups) { 1441 return filterGroups(ng -> TextUtils.equals(groupKey, ng.getGroupKey()), notificationGroups); 1442 } 1443 getGroupsWithSeenState(boolean isSeen, List<NotificationGroup> notificationGroups)1444 private List<NotificationGroup> getGroupsWithSeenState(boolean isSeen, 1445 List<NotificationGroup> notificationGroups) { 1446 return filterGroups(ng -> ng.isSeen() == isSeen, notificationGroups); 1447 } 1448 filterGroups(Function<NotificationGroup, Boolean> filter, List<NotificationGroup> notificationGroups)1449 private List<NotificationGroup> filterGroups(Function<NotificationGroup, Boolean> filter, 1450 List<NotificationGroup> notificationGroups) { 1451 return notificationGroups.stream().filter(filter::apply).toList(); 1452 } 1453 canBubble(int index)1454 private boolean canBubble(int index) { 1455 return index % 4 == 0; 1456 } 1457 isVisuallyInterruptive(int index)1458 private boolean isVisuallyInterruptive(int index) { 1459 return index % 4 == 0; 1460 } 1461 isConversation(int index)1462 private boolean isConversation(int index) { 1463 return index % 4 == 0; 1464 } 1465 getRankingAdjustment(int index)1466 private int getRankingAdjustment(int index) { 1467 return index % 3 - 1; 1468 } 1469 isBubble(int index)1470 private boolean isBubble(int index) { 1471 return index % 4 == 0; 1472 } 1473 } 1474