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