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 51 import androidx.test.ext.junit.runners.AndroidJUnit4; 52 import androidx.test.platform.app.InstrumentationRegistry; 53 54 import org.junit.Before; 55 import org.junit.Rule; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 import org.mockito.InOrder; 59 import org.mockito.Mock; 60 import org.mockito.Mockito; 61 import org.mockito.MockitoAnnotations; 62 63 import java.util.ArrayList; 64 import java.util.Arrays; 65 import java.util.HashMap; 66 import java.util.HashSet; 67 import java.util.List; 68 import java.util.Map; 69 import java.util.Set; 70 import java.util.stream.Collectors; 71 72 @RunWith(AndroidJUnit4.class) 73 public class PreprocessingManagerTest { 74 75 private static final String PKG = "com.package.PREPROCESSING_MANAGER_TEST"; 76 private static final String OP_PKG = "OpPackage"; 77 private static final int ID = 1; 78 private static final String TAG = "Tag"; 79 private static final int UID = 2; 80 private static final int INITIAL_PID = 3; 81 private static final String CHANNEL_ID = "CHANNEL_ID"; 82 private static final String CONTENT_TITLE = "CONTENT_TITLE"; 83 private static final String OVERRIDE_GROUP_KEY = "OVERRIDE_GROUP_KEY"; 84 private static final long POST_TIME = 12345l; 85 private static final UserHandle USER_HANDLE = new UserHandle(12); 86 private static final String GROUP_KEY_A = "GROUP_KEY_A"; 87 private static final String GROUP_KEY_B = "GROUP_KEY_B"; 88 private static final String GROUP_KEY_C = "GROUP_KEY_C"; 89 private static final int MAX_STRING_LENGTH = 10; 90 private static final int DEFAULT_MIN_GROUPING_THRESHOLD = 4; 91 @Rule 92 public final TestableContext mContext = new TestableContext( 93 InstrumentationRegistry.getInstrumentation().getTargetContext()); 94 @Mock 95 private StatusBarNotification mStatusBarNotification1; 96 @Mock 97 private StatusBarNotification mStatusBarNotification2; 98 @Mock 99 private StatusBarNotification mStatusBarNotification3; 100 @Mock 101 private StatusBarNotification mStatusBarNotification4; 102 @Mock 103 private StatusBarNotification mStatusBarNotification5; 104 @Mock 105 private StatusBarNotification mStatusBarNotification6; 106 @Mock 107 private StatusBarNotification mStatusBarNotification7; 108 @Mock 109 private StatusBarNotification mStatusBarNotification8; 110 @Mock 111 private StatusBarNotification mStatusBarNotification9; 112 @Mock 113 private StatusBarNotification mStatusBarNotification10; 114 @Mock 115 private StatusBarNotification mStatusBarNotification11; 116 @Mock 117 private StatusBarNotification mStatusBarNotification12; 118 @Mock 119 private StatusBarNotification mAdditionalStatusBarNotification; 120 @Mock 121 private StatusBarNotification mSummaryAStatusBarNotification; 122 @Mock 123 private StatusBarNotification mSummaryBStatusBarNotification; 124 @Mock 125 private StatusBarNotification mSummaryCStatusBarNotification; 126 @Mock 127 private CarUxRestrictions mCarUxRestrictions; 128 @Mock 129 private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper; 130 @Mock 131 private PreprocessingManager.CallStateListener mCallStateListener1; 132 @Mock 133 private PreprocessingManager.CallStateListener mCallStateListener2; 134 @Mock 135 private Notification mMediaNotification; 136 @Mock 137 private Notification mSummaryNotification; 138 @Mock 139 private PackageManager mPackageManager; 140 @Mock 141 private NotificationDataManager mNotificationDataManager; 142 143 private PreprocessingManager mPreprocessingManager; 144 145 private Notification mForegroundNotification; 146 private Notification mBackgroundNotification; 147 private Notification mNavigationNotification; 148 149 // Following AlertEntry var names describe the type of notifications they wrap. 150 private AlertEntry mLessImportantBackground; 151 private AlertEntry mLessImportantForeground; 152 private AlertEntry mMedia; 153 private AlertEntry mNavigation; 154 private AlertEntry mImportantBackground; 155 private AlertEntry mImportantForeground; 156 private AlertEntry mImportantForeground2; 157 private AlertEntry mImportantForeground3; 158 private AlertEntry mImportantForeground4; 159 private AlertEntry mImportantForeground5; 160 private AlertEntry mImportantForeground6; 161 private AlertEntry mImportantForeground7; 162 163 private List<AlertEntry> mAlertEntries; 164 private Map<String, AlertEntry> mAlertEntriesMap; 165 private NotificationListenerService.RankingMap mRankingMap; 166 167 @Before setup()168 public void setup() throws PackageManager.NameNotFoundException { 169 MockitoAnnotations.initMocks(this); 170 171 // prevents less important foreground notifications from not being filtered due to the 172 // application and package setup. 173 PackageInfo packageInfo = mock(PackageInfo.class); 174 ApplicationInfo applicationInfo = mock(ApplicationInfo.class); 175 packageInfo.packageName = PKG; 176 when(applicationInfo.isPrivilegedApp()).thenReturn(true); 177 when(applicationInfo.isSystemApp()).thenReturn(true); 178 when(applicationInfo.isSignedWithPlatformKey()).thenReturn(true); 179 packageInfo.applicationInfo = applicationInfo; 180 when(mPackageManager.getPackageInfoAsUser(anyString(), anyInt(), anyInt())).thenReturn( 181 packageInfo); 182 mContext.setMockPackageManager(mPackageManager); 183 184 mPreprocessingManager.refreshInstance(); 185 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 186 187 mForegroundNotification = generateNotification(/* isForeground= */ true, 188 /* isNavigation= */ false, /* isGroupSummary= */ true); 189 mBackgroundNotification = generateNotification(/* isForeground= */ false, 190 /* isNavigation= */ false, /* isGroupSummary= */ true); 191 mNavigationNotification = generateNotification(/* isForeground= */ true, 192 /* isNavigation= */ true, /* isGroupSummary= */ true); 193 194 when(mMediaNotification.isMediaNotification()).thenReturn(true); 195 196 // Key describes the notification that the StatusBarNotification contains. 197 when(mStatusBarNotification1.getKey()).thenReturn("KEY_LESS_IMPORTANT_BACKGROUND"); 198 when(mStatusBarNotification2.getKey()).thenReturn("KEY_LESS_IMPORTANT_FOREGROUND"); 199 when(mStatusBarNotification3.getKey()).thenReturn("KEY_MEDIA"); 200 when(mStatusBarNotification4.getKey()).thenReturn("KEY_NAVIGATION"); 201 when(mStatusBarNotification5.getKey()).thenReturn("KEY_IMPORTANT_BACKGROUND"); 202 when(mStatusBarNotification6.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND"); 203 when(mStatusBarNotification7.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_2"); 204 when(mStatusBarNotification8.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_3"); 205 when(mStatusBarNotification9.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_4"); 206 when(mStatusBarNotification10.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_5"); 207 when(mStatusBarNotification11.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_6"); 208 when(mStatusBarNotification12.getKey()).thenReturn("KEY_IMPORTANT_FOREGROUND_7"); 209 when(mSummaryAStatusBarNotification.getKey()).thenReturn("KEY_SUMMARY_A"); 210 when(mSummaryBStatusBarNotification.getKey()).thenReturn("KEY_SUMMARY_B"); 211 when(mSummaryCStatusBarNotification.getKey()).thenReturn("KEY_SUMMARY_C"); 212 213 when(mStatusBarNotification1.getGroupKey()).thenReturn(GROUP_KEY_A); 214 when(mStatusBarNotification2.getGroupKey()).thenReturn(GROUP_KEY_B); 215 when(mStatusBarNotification3.getGroupKey()).thenReturn(GROUP_KEY_A); 216 when(mStatusBarNotification4.getGroupKey()).thenReturn(GROUP_KEY_B); 217 when(mStatusBarNotification5.getGroupKey()).thenReturn(GROUP_KEY_B); 218 when(mStatusBarNotification6.getGroupKey()).thenReturn(GROUP_KEY_C); 219 when(mSummaryAStatusBarNotification.getGroupKey()).thenReturn(GROUP_KEY_A); 220 when(mSummaryBStatusBarNotification.getGroupKey()).thenReturn(GROUP_KEY_B); 221 when(mSummaryCStatusBarNotification.getGroupKey()).thenReturn(GROUP_KEY_C); 222 223 when(mStatusBarNotification1.getNotification()).thenReturn(mBackgroundNotification); 224 when(mStatusBarNotification2.getNotification()).thenReturn(mForegroundNotification); 225 when(mStatusBarNotification3.getNotification()).thenReturn(mMediaNotification); 226 when(mStatusBarNotification4.getNotification()).thenReturn(mNavigationNotification); 227 when(mStatusBarNotification5.getNotification()).thenReturn(mBackgroundNotification); 228 when(mStatusBarNotification6.getNotification()).thenReturn(mForegroundNotification); 229 when(mStatusBarNotification7.getNotification()).thenReturn(mForegroundNotification); 230 when(mStatusBarNotification8.getNotification()).thenReturn(mForegroundNotification); 231 when(mStatusBarNotification9.getNotification()).thenReturn(mForegroundNotification); 232 when(mStatusBarNotification10.getNotification()).thenReturn(mForegroundNotification); 233 when(mStatusBarNotification11.getNotification()).thenReturn(mForegroundNotification); 234 when(mStatusBarNotification12.getNotification()).thenReturn(mForegroundNotification); 235 when(mSummaryAStatusBarNotification.getNotification()).thenReturn(mSummaryNotification); 236 when(mSummaryBStatusBarNotification.getNotification()).thenReturn(mSummaryNotification); 237 when(mSummaryCStatusBarNotification.getNotification()).thenReturn(mSummaryNotification); 238 239 when(mStatusBarNotification1.getPackageName()).thenReturn(PKG); 240 when(mStatusBarNotification2.getPackageName()).thenReturn(PKG); 241 when(mStatusBarNotification3.getPackageName()).thenReturn(PKG); 242 when(mStatusBarNotification4.getPackageName()).thenReturn(PKG); 243 when(mStatusBarNotification5.getPackageName()).thenReturn(PKG); 244 when(mStatusBarNotification6.getPackageName()).thenReturn(PKG); 245 when(mStatusBarNotification7.getPackageName()).thenReturn(PKG); 246 when(mStatusBarNotification8.getPackageName()).thenReturn(PKG); 247 when(mStatusBarNotification9.getPackageName()).thenReturn(PKG); 248 when(mStatusBarNotification10.getPackageName()).thenReturn(PKG); 249 when(mStatusBarNotification11.getPackageName()).thenReturn(PKG); 250 when(mStatusBarNotification12.getPackageName()).thenReturn(PKG); 251 when(mSummaryAStatusBarNotification.getPackageName()).thenReturn(PKG); 252 when(mSummaryBStatusBarNotification.getPackageName()).thenReturn(PKG); 253 when(mSummaryCStatusBarNotification.getPackageName()).thenReturn(PKG); 254 255 when(mSummaryNotification.isGroupSummary()).thenReturn(true); 256 257 // Always start system with no phone calls in progress. 258 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 259 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_IDLE); 260 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 261 262 initTestData(/* includeAdditionalNotifs= */ false); 263 } 264 265 @Test onFilter_showLessImportantNotifications_doesNotFilterNotifications()266 public void onFilter_showLessImportantNotifications_doesNotFilterNotifications() { 267 List<AlertEntry> unfiltered = mAlertEntries.stream().collect(Collectors.toList()); 268 mPreprocessingManager 269 .filter(/* showLessImportantNotifications= */ true, mAlertEntries, mRankingMap); 270 271 assertThat(mAlertEntries.equals(unfiltered)).isTrue(); 272 } 273 274 @Test onFilter_dontShowLessImportantNotifications_filtersLessImportantForeground()275 public void onFilter_dontShowLessImportantNotifications_filtersLessImportantForeground() 276 throws PackageManager.NameNotFoundException { 277 mPreprocessingManager 278 .filter( /* showLessImportantNotifications= */ false, mAlertEntries, mRankingMap); 279 280 assertThat(mAlertEntries.contains(mLessImportantBackground)).isTrue(); 281 assertThat(mAlertEntries.contains(mLessImportantForeground)).isFalse(); 282 } 283 284 @Test onFilter_dontShowLessImportantNotifications_doesNotFilterMoreImportant()285 public void onFilter_dontShowLessImportantNotifications_doesNotFilterMoreImportant() { 286 mPreprocessingManager 287 .filter(/* showLessImportantNotifications= */ false, mAlertEntries, mRankingMap); 288 289 assertThat(mAlertEntries.contains(mImportantBackground)).isTrue(); 290 assertThat(mAlertEntries.contains(mImportantForeground)).isTrue(); 291 } 292 293 @Test onFilter_dontShowLessImportantNotifications_filtersMediaAndNavigation()294 public void onFilter_dontShowLessImportantNotifications_filtersMediaAndNavigation() { 295 mPreprocessingManager 296 .filter(/* showLessImportantNotifications= */ false, mAlertEntries, mRankingMap); 297 298 assertThat(mAlertEntries.contains(mMedia)).isFalse(); 299 assertThat(mAlertEntries.contains(mNavigation)).isFalse(); 300 } 301 302 @Test onFilter_doShowLessImportantNotifications_doesNotFilterMediaOrNavigation()303 public void onFilter_doShowLessImportantNotifications_doesNotFilterMediaOrNavigation() { 304 mPreprocessingManager 305 .filter(/* showLessImportantNotifications= */ true, mAlertEntries, mRankingMap); 306 307 assertThat(mAlertEntries.contains(mMedia)).isTrue(); 308 assertThat(mAlertEntries.contains(mNavigation)).isTrue(); 309 } 310 311 @Test onFilter_doShowLessImportantNotifications_filtersCalls()312 public void onFilter_doShowLessImportantNotifications_filtersCalls() { 313 StatusBarNotification callSBN = mock(StatusBarNotification.class); 314 Notification callNotification = new Notification(); 315 callNotification.category = Notification.CATEGORY_CALL; 316 when(callSBN.getNotification()).thenReturn(callNotification); 317 List<AlertEntry> entries = new ArrayList<>(); 318 entries.add(new AlertEntry(callSBN)); 319 320 mPreprocessingManager.filter(true, entries, mRankingMap); 321 assertThat(entries).isEmpty(); 322 } 323 324 @Test onFilter_dontShowLessImportantNotifications_filtersCalls()325 public void onFilter_dontShowLessImportantNotifications_filtersCalls() { 326 StatusBarNotification callSBN = mock(StatusBarNotification.class); 327 Notification callNotification = new Notification(); 328 callNotification.category = Notification.CATEGORY_CALL; 329 when(callSBN.getNotification()).thenReturn(callNotification); 330 List<AlertEntry> entries = new ArrayList<>(); 331 entries.add(new AlertEntry(callSBN)); 332 333 mPreprocessingManager.filter(false, entries, mRankingMap); 334 assertThat(entries).isEmpty(); 335 } 336 337 @Test onOptimizeForDriving_alertEntryHasNonMessageNotification_trimsNotificationTexts()338 public void onOptimizeForDriving_alertEntryHasNonMessageNotification_trimsNotificationTexts() { 339 when(mCarUxRestrictions.getMaxRestrictedStringLength()).thenReturn(MAX_STRING_LENGTH); 340 when(mCarUxRestrictionManagerWrapper.getCurrentCarUxRestrictions()) 341 .thenReturn(mCarUxRestrictions); 342 mPreprocessingManager.setCarUxRestrictionManagerWrapper(mCarUxRestrictionManagerWrapper); 343 344 Notification nonMessageNotification = generateNotification(/* isForeground= */ true, 345 /* isNavigation= */ true, /* isGroupSummary= */ true); 346 nonMessageNotification.extras 347 .putString(Notification.EXTRA_TITLE, generateStringOfLength(100)); 348 nonMessageNotification.extras 349 .putString(Notification.EXTRA_TEXT, generateStringOfLength(100)); 350 nonMessageNotification.extras 351 .putString(Notification.EXTRA_TITLE_BIG, generateStringOfLength(100)); 352 nonMessageNotification.extras 353 .putString(Notification.EXTRA_SUMMARY_TEXT, generateStringOfLength(100)); 354 355 when(mNavigation.getNotification()).thenReturn(nonMessageNotification); 356 357 AlertEntry optimized = mPreprocessingManager.optimizeForDriving(mNavigation); 358 Bundle trimmed = optimized.getNotification().extras; 359 360 for (String key : trimmed.keySet()) { 361 switch (key) { 362 case Notification.EXTRA_TITLE: 363 case Notification.EXTRA_TEXT: 364 case Notification.EXTRA_TITLE_BIG: 365 case Notification.EXTRA_SUMMARY_TEXT: 366 CharSequence text = trimmed.getCharSequence(key); 367 assertThat(text.length() <= MAX_STRING_LENGTH).isTrue(); 368 default: 369 continue; 370 } 371 } 372 } 373 374 @Test onOptimizeForDriving_alertEntryHasMessageNotification_doesNotTrimMessageTexts()375 public void onOptimizeForDriving_alertEntryHasMessageNotification_doesNotTrimMessageTexts() { 376 when(mCarUxRestrictions.getMaxRestrictedStringLength()).thenReturn(MAX_STRING_LENGTH); 377 when(mCarUxRestrictionManagerWrapper.getCurrentCarUxRestrictions()) 378 .thenReturn(mCarUxRestrictions); 379 mPreprocessingManager.setCarUxRestrictionManagerWrapper(mCarUxRestrictionManagerWrapper); 380 381 Notification messageNotification = generateNotification(/* isForeground= */ true, 382 /* isNavigation= */ true, /* isGroupSummary= */ true); 383 messageNotification.extras 384 .putString(Notification.EXTRA_TITLE, generateStringOfLength(100)); 385 messageNotification.extras 386 .putString(Notification.EXTRA_TEXT, generateStringOfLength(100)); 387 messageNotification.extras 388 .putString(Notification.EXTRA_TITLE_BIG, generateStringOfLength(100)); 389 messageNotification.extras 390 .putString(Notification.EXTRA_SUMMARY_TEXT, generateStringOfLength(100)); 391 messageNotification.category = Notification.CATEGORY_MESSAGE; 392 393 when(mImportantForeground.getNotification()).thenReturn(messageNotification); 394 395 AlertEntry optimized = mPreprocessingManager.optimizeForDriving(mImportantForeground); 396 Bundle trimmed = optimized.getNotification().extras; 397 398 for (String key : trimmed.keySet()) { 399 switch (key) { 400 case Notification.EXTRA_TITLE: 401 case Notification.EXTRA_TEXT: 402 case Notification.EXTRA_TITLE_BIG: 403 case Notification.EXTRA_SUMMARY_TEXT: 404 CharSequence text = trimmed.getCharSequence(key); 405 assertThat(text.length() <= MAX_STRING_LENGTH).isFalse(); 406 default: 407 continue; 408 } 409 } 410 } 411 412 @Test onGroup_groupsNotificationsByGroupKey()413 public void onGroup_groupsNotificationsByGroupKey() { 414 setConfig(/* recentOld= */ true, /* launcherIcon= */ true, /* groupingThreshold= */ 2); 415 PreprocessingManager.refreshInstance(); 416 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 417 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 418 String[] actualGroupKeys = new String[groupResult.size()]; 419 String[] expectedGroupKeys = {GROUP_KEY_A, GROUP_KEY_B, GROUP_KEY_C}; 420 421 for (int i = 0; i < groupResult.size(); i++) { 422 actualGroupKeys[i] = groupResult.get(i).getGroupKey(); 423 } 424 425 Arrays.sort(actualGroupKeys); 426 Arrays.sort(expectedGroupKeys); 427 428 assertThat(actualGroupKeys).isEqualTo(expectedGroupKeys); 429 } 430 431 @Test onGroup_highGroupingThreshold_noGroups()432 public void onGroup_highGroupingThreshold_noGroups() { 433 setConfig(/* recentOld= */ true, /* launcherIcon= */ true, DEFAULT_MIN_GROUPING_THRESHOLD); 434 PreprocessingManager.refreshInstance(); 435 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 436 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 437 String[] actualGroupKeys = new String[groupResult.size()]; 438 String[] expectedGroupKeys = {GROUP_KEY_A, GROUP_KEY_B, GROUP_KEY_B, GROUP_KEY_C}; 439 440 for (int i = 0; i < groupResult.size(); i++) { 441 actualGroupKeys[i] = groupResult.get(i).getGroupKey(); 442 } 443 444 Arrays.sort(actualGroupKeys); 445 Arrays.sort(expectedGroupKeys); 446 447 assertThat(actualGroupKeys).isEqualTo(expectedGroupKeys); 448 } 449 450 @Test onGroup_groupsNotificationsBySeenUnseen()451 public void onGroup_groupsNotificationsBySeenUnseen() { 452 setConfig(/* recentOld= */ true, /* launcherIcon= */ true, DEFAULT_MIN_GROUPING_THRESHOLD); 453 initTestData(/* includeAdditionalNotifs= */ true); 454 PreprocessingManager.refreshInstance(); 455 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 456 when(mNotificationDataManager.isNotificationSeen(mLessImportantForeground)) 457 .thenReturn(true); 458 when(mNotificationDataManager.isNotificationSeen(mLessImportantBackground)) 459 .thenReturn(true); 460 when(mNotificationDataManager.isNotificationSeen(mMedia)).thenReturn(true); 461 when(mNotificationDataManager.isNotificationSeen(mImportantBackground)).thenReturn(true); 462 when(mNotificationDataManager.isNotificationSeen(mImportantForeground)).thenReturn(true); 463 when(mNotificationDataManager.isNotificationSeen(mImportantForeground2)).thenReturn(true); 464 when(mNotificationDataManager.isNotificationSeen(mImportantForeground3)).thenReturn(true); 465 when(mNotificationDataManager.isNotificationSeen(mImportantForeground4)).thenReturn(false); 466 when(mNotificationDataManager.isNotificationSeen(mImportantForeground5)).thenReturn(false); 467 when(mNotificationDataManager.isNotificationSeen(mImportantForeground6)).thenReturn(false); 468 when(mNotificationDataManager.isNotificationSeen(mImportantForeground7)).thenReturn(false); 469 when(mNotificationDataManager.isNotificationSeen(mNavigation)).thenReturn(false); 470 when(mStatusBarNotification1.getGroupKey()).thenReturn(GROUP_KEY_A); 471 when(mStatusBarNotification2.getGroupKey()).thenReturn(GROUP_KEY_A); 472 when(mStatusBarNotification3.getGroupKey()).thenReturn(GROUP_KEY_A); 473 when(mStatusBarNotification4.getGroupKey()).thenReturn(GROUP_KEY_A); 474 when(mStatusBarNotification5.getGroupKey()).thenReturn(GROUP_KEY_A); 475 when(mStatusBarNotification6.getGroupKey()).thenReturn(GROUP_KEY_A); 476 when(mStatusBarNotification7.getGroupKey()).thenReturn(GROUP_KEY_A); 477 when(mStatusBarNotification8.getGroupKey()).thenReturn(GROUP_KEY_A); 478 when(mStatusBarNotification9.getGroupKey()).thenReturn(GROUP_KEY_A); 479 when(mStatusBarNotification10.getGroupKey()).thenReturn(GROUP_KEY_A); 480 when(mStatusBarNotification11.getGroupKey()).thenReturn(GROUP_KEY_A); 481 when(mStatusBarNotification12.getGroupKey()).thenReturn(GROUP_KEY_A); 482 483 mPreprocessingManager.setNotificationDataManager(mNotificationDataManager); 484 485 Set expectedResultUnseen = new HashSet(); 486 expectedResultUnseen.add(mImportantBackground.getKey()); 487 expectedResultUnseen.add(mNavigation.getKey()); 488 expectedResultUnseen.add(mImportantForeground4.getKey()); 489 expectedResultUnseen.add(mImportantForeground5.getKey()); 490 expectedResultUnseen.add(mImportantForeground6.getKey()); 491 expectedResultUnseen.add(mImportantForeground7.getKey()); 492 Set expectedResultSeen = new HashSet(); 493 expectedResultSeen.add(mImportantBackground.getKey()); 494 expectedResultSeen.add(mLessImportantForeground.getKey()); 495 expectedResultSeen.add(mImportantForeground2.getKey()); 496 expectedResultSeen.add(mImportantForeground3.getKey()); 497 expectedResultSeen.add(mMedia.getKey()); 498 expectedResultSeen.add(mImportantForeground.getKey()); 499 500 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 501 Set actualResultSeen = new HashSet(); 502 Set actualResultUnseen = new HashSet(); 503 for (int j = 0; j < groupResult.size(); j++) { 504 NotificationGroup group = groupResult.get(j); 505 List<AlertEntry> childNotifications = group.getChildNotifications(); 506 for (int i = 0; i < childNotifications.size(); i++) { 507 if (group.isSeen()) { 508 actualResultSeen.add(childNotifications.get(i).getKey()); 509 } else { 510 actualResultUnseen.add(childNotifications.get(i).getKey()); 511 } 512 } 513 if (group.getGroupSummaryNotification() != null) { 514 if (group.isSeen()) { 515 actualResultSeen.add(group.getGroupSummaryNotification().getKey()); 516 } else { 517 actualResultUnseen.add(group.getGroupSummaryNotification().getKey()); 518 } 519 } 520 } 521 assertThat(actualResultSeen).isEqualTo(expectedResultSeen); 522 assertThat(actualResultUnseen).isEqualTo(expectedResultUnseen); 523 } 524 525 @Test onGroup_autoGeneratedGroupWithNoGroupChildren_doesNotShowGroupSummary()526 public void onGroup_autoGeneratedGroupWithNoGroupChildren_doesNotShowGroupSummary() { 527 List<AlertEntry> list = new ArrayList<>(); 528 list.add(getEmptyAutoGeneratedGroupSummary()); 529 List<NotificationGroup> groupResult = mPreprocessingManager.group(list); 530 531 assertThat(groupResult.size() == 0).isTrue(); 532 } 533 534 @Test addCallStateListener_preCall_triggerChanges()535 public void addCallStateListener_preCall_triggerChanges() { 536 InOrder listenerInOrder = Mockito.inOrder(mCallStateListener1); 537 mPreprocessingManager.addCallStateListener(mCallStateListener1); 538 listenerInOrder.verify(mCallStateListener1).onCallStateChanged(false); 539 540 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 541 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_OFFHOOK); 542 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 543 544 listenerInOrder.verify(mCallStateListener1).onCallStateChanged(true); 545 } 546 547 @Test addCallStateListener_midCall_triggerChanges()548 public void addCallStateListener_midCall_triggerChanges() { 549 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 550 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_OFFHOOK); 551 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 552 553 mPreprocessingManager.addCallStateListener(mCallStateListener1); 554 555 verify(mCallStateListener1).onCallStateChanged(true); 556 } 557 558 @Test addCallStateListener_postCall_triggerChanges()559 public void addCallStateListener_postCall_triggerChanges() { 560 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 561 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_OFFHOOK); 562 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 563 564 intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 565 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_IDLE); 566 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 567 568 mPreprocessingManager.addCallStateListener(mCallStateListener1); 569 570 verify(mCallStateListener1).onCallStateChanged(false); 571 } 572 573 @Test addSameCallListenerTwice_dedupedCorrectly()574 public void addSameCallListenerTwice_dedupedCorrectly() { 575 mPreprocessingManager.addCallStateListener(mCallStateListener1); 576 577 verify(mCallStateListener1).onCallStateChanged(false); 578 mPreprocessingManager.addCallStateListener(mCallStateListener1); 579 580 verify(mCallStateListener1, times(1)).onCallStateChanged(false); 581 } 582 583 @Test removeCallStateListener_midCall_triggerChanges()584 public void removeCallStateListener_midCall_triggerChanges() { 585 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 586 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_OFFHOOK); 587 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 588 589 mPreprocessingManager.addCallStateListener(mCallStateListener1); 590 // Should get triggered with true before calling removeCallStateListener 591 mPreprocessingManager.removeCallStateListener(mCallStateListener1); 592 593 intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 594 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_IDLE); 595 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 596 597 verify(mCallStateListener1, never()).onCallStateChanged(false); 598 } 599 600 @Test multipleCallStateListeners_triggeredAppropriately()601 public void multipleCallStateListeners_triggeredAppropriately() { 602 InOrder listenerInOrder1 = Mockito.inOrder(mCallStateListener1); 603 InOrder listenerInOrder2 = Mockito.inOrder(mCallStateListener2); 604 mPreprocessingManager.addCallStateListener(mCallStateListener1); 605 listenerInOrder1.verify(mCallStateListener1).onCallStateChanged(false); 606 607 Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 608 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_OFFHOOK); 609 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 610 611 mPreprocessingManager.addCallStateListener(mCallStateListener2); 612 mPreprocessingManager.removeCallStateListener(mCallStateListener1); 613 614 listenerInOrder1.verify(mCallStateListener1).onCallStateChanged(true); 615 listenerInOrder2.verify(mCallStateListener2).onCallStateChanged(true); 616 617 intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); 618 intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_IDLE); 619 mPreprocessingManager.mIntentReceiver.onReceive(mContext, intent); 620 621 // only listener 2 should be triggered w/ false 622 listenerInOrder1.verifyNoMoreInteractions(); 623 listenerInOrder2.verify(mCallStateListener2).onCallStateChanged(false); 624 } 625 626 @Test onGroup_removesNotificationGroupWithOnlySummaryNotification()627 public void onGroup_removesNotificationGroupWithOnlySummaryNotification() { 628 List<AlertEntry> list = new ArrayList<>(); 629 list.add(new AlertEntry(mSummaryCStatusBarNotification)); 630 List<NotificationGroup> groupResult = mPreprocessingManager.group(list); 631 632 assertThat(groupResult.isEmpty()).isTrue(); 633 } 634 635 @Test onGroup_splitsNotificationsBySeenAndUnseen()636 public void onGroup_splitsNotificationsBySeenAndUnseen() { 637 List<AlertEntry> list = new ArrayList<>(); 638 list.add(new AlertEntry(mSummaryCStatusBarNotification)); 639 640 List<NotificationGroup> groupResult = mPreprocessingManager.group(list); 641 642 assertThat(groupResult.isEmpty()).isTrue(); 643 } 644 645 @Test onGroup_childNotificationHasTimeStamp_groupHasMostRecentTimeStamp()646 public void onGroup_childNotificationHasTimeStamp_groupHasMostRecentTimeStamp() { 647 mBackgroundNotification.when = 0; 648 mForegroundNotification.when = 1; 649 mNavigationNotification.when = 2; 650 651 mBackgroundNotification.extras.putBoolean(Notification.EXTRA_SHOW_WHEN, true); 652 mForegroundNotification.extras.putBoolean(Notification.EXTRA_SHOW_WHEN, true); 653 mNavigationNotification.extras.putBoolean(Notification.EXTRA_SHOW_WHEN, true); 654 655 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 656 657 groupResult.forEach(group -> { 658 AlertEntry groupSummaryNotification = group.getGroupSummaryNotification(); 659 if (groupSummaryNotification != null 660 && groupSummaryNotification.getNotification() != null) { 661 assertThat(groupSummaryNotification.getNotification() 662 .extras.getBoolean(Notification.EXTRA_SHOW_WHEN)).isTrue(); 663 } 664 }); 665 } 666 667 @Test onRank_ranksNotificationGroups()668 public void onRank_ranksNotificationGroups() { 669 setConfig(/* recentOld= */ true, /* launcherIcon= */ true, /* groupThreshold= */ 2); 670 PreprocessingManager.refreshInstance(); 671 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 672 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 673 List<NotificationGroup> rankResult = mPreprocessingManager.rank(groupResult, mRankingMap); 674 675 // generateRankingMap ranked the notifications in the reverse order. 676 String[] expectedOrder = { 677 GROUP_KEY_C, 678 GROUP_KEY_B, 679 GROUP_KEY_A 680 }; 681 682 for (int i = 0; i < rankResult.size(); i++) { 683 String actualGroupKey = rankResult.get(i).getGroupKey(); 684 String expectedGroupKey = expectedOrder[i]; 685 686 assertThat(actualGroupKey).isEqualTo(expectedGroupKey); 687 } 688 } 689 690 @Test onRank_ranksNotificationsInEachGroup()691 public void onRank_ranksNotificationsInEachGroup() { 692 setConfig(/* recentOld= */true, /* launcherIcon= */ true, /* groupThreshold= */ 2); 693 PreprocessingManager.refreshInstance(); 694 mPreprocessingManager = PreprocessingManager.getInstance(mContext); 695 List<NotificationGroup> groupResult = mPreprocessingManager.group(mAlertEntries); 696 List<NotificationGroup> rankResult = mPreprocessingManager.rank(groupResult, mRankingMap); 697 NotificationGroup groupB = rankResult.get(1); 698 699 // first make sure that we have Group B 700 assertThat(groupB.getGroupKey()).isEqualTo(GROUP_KEY_B); 701 702 // generateRankingMap ranked the non-background notifications in the reverse order 703 String[] expectedOrder = { 704 "KEY_NAVIGATION", 705 "KEY_LESS_IMPORTANT_FOREGROUND" 706 }; 707 708 for (int i = 0; i < groupB.getChildNotifications().size(); i++) { 709 String actualKey = groupB.getChildNotifications().get(i).getKey(); 710 String expectedGroupKey = expectedOrder[i]; 711 712 assertThat(actualKey).isEqualTo(expectedGroupKey); 713 } 714 } 715 716 @Test onAdditionalGroupAndRank_isGroupSummary_returnsTheSameGroupsAsStandardGroup()717 public void onAdditionalGroupAndRank_isGroupSummary_returnsTheSameGroupsAsStandardGroup() { 718 Notification additionalNotification = generateNotification(/* isForeground= */ true, 719 /* isNavigation= */ false, /* isGroupSummary= */ true); 720 additionalNotification.category = Notification.CATEGORY_MESSAGE; 721 when(mAdditionalStatusBarNotification.getKey()).thenReturn("ADDITIONAL"); 722 when(mAdditionalStatusBarNotification.getGroupKey()).thenReturn(GROUP_KEY_C); 723 when(mAdditionalStatusBarNotification.getNotification()).thenReturn(additionalNotification); 724 AlertEntry additionalAlertEntry = new AlertEntry(mAdditionalStatusBarNotification); 725 726 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 727 List<AlertEntry> copy = mPreprocessingManager.filter(/* showLessImportantNotifications= */ 728 false, new ArrayList<>(mAlertEntries), mRankingMap); 729 copy.add(additionalAlertEntry); 730 copy.add(new AlertEntry(mSummaryCStatusBarNotification)); 731 List<NotificationGroup> expected = mPreprocessingManager.group(copy); 732 String[] expectedKeys = new String[expected.size()]; 733 for (int i = 0; i < expectedKeys.length; i++) { 734 expectedKeys[i] = expected.get(i).getGroupKey(); 735 } 736 737 List<NotificationGroup> actual = mPreprocessingManager 738 .additionalGroupAndRank(additionalAlertEntry, mRankingMap, /* isUpdate= */ false); 739 740 String[] actualKeys = new String[actual.size()]; 741 for (int i = 0; i < actualKeys.length; i++) { 742 actualKeys[i] = actual.get(i).getGroupKey(); 743 } 744 // We do not care about the order since they are not ranked yet. 745 Arrays.sort(actualKeys); 746 Arrays.sort(expectedKeys); 747 assertThat(actualKeys).isEqualTo(expectedKeys); 748 } 749 750 @Test onAdditionalGroupAndRank_isGroupSummary_maintainsPreviousRanking()751 public void onAdditionalGroupAndRank_isGroupSummary_maintainsPreviousRanking() { 752 Map<String, AlertEntry> testCopy = new HashMap<>(mAlertEntriesMap); 753 // Seed the list with the notifications 754 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 755 756 String key = "NEW_KEY"; 757 String groupKey = "NEW_GROUP_KEY"; 758 Notification newNotification = generateNotification(/* isForeground= */ false, 759 /* isNavigation= */ false, /* isGroupSummary= */ true); 760 StatusBarNotification newSbn = mock(StatusBarNotification.class); 761 when(newSbn.getNotification()).thenReturn(newNotification); 762 when(newSbn.getKey()).thenReturn(key); 763 when(newSbn.getGroupKey()).thenReturn(groupKey); 764 765 AlertEntry newEntry = new AlertEntry(newSbn); 766 767 // Change the ordering, add a new notification and validate that the existing 768 // notifications don't reorder 769 AlertEntry first = mAlertEntries.get(0); 770 mAlertEntries.remove(0); 771 mAlertEntries.add(first); 772 773 List<NotificationGroup> additionalRanked = mPreprocessingManager.additionalGroupAndRank( 774 newEntry, generateRankingMap(mAlertEntries), /* isUpdate= */ false) 775 .stream() 776 .filter(g -> !g.getGroupKey().equals(groupKey)) 777 .collect(Collectors.toList()); 778 779 List<NotificationGroup> standardRanked = mPreprocessingManager.rank( 780 mPreprocessingManager.process(/* showLessImportantNotifications = */ false, 781 testCopy, mRankingMap), mRankingMap); 782 783 assertThat(additionalRanked.size()).isEqualTo(standardRanked.size()); 784 785 for (int i = 0; i < additionalRanked.size(); i++) { 786 assertThat(additionalRanked.get(i).getGroupKey()).isEqualTo( 787 standardRanked.get(i).getGroupKey()); 788 } 789 } 790 791 @Test onAdditionalGroupAndRank_isGroupSummary_prependsHighRankNotification()792 public void onAdditionalGroupAndRank_isGroupSummary_prependsHighRankNotification() { 793 // Seed the list 794 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 795 796 String key = "NEW_KEY"; 797 String groupKey = "NEW_GROUP_KEY"; 798 Notification newNotification = generateNotification(/* isForeground= */ false, 799 /* isNavigation= */ false, /* isGroupSummary= */ true); 800 StatusBarNotification newSbn = mock(StatusBarNotification.class); 801 when(newSbn.getNotification()).thenReturn(newNotification); 802 when(newSbn.getKey()).thenReturn(key); 803 when(newSbn.getGroupKey()).thenReturn(groupKey); 804 805 AlertEntry newEntry = new AlertEntry(newSbn); 806 mAlertEntries.add(newEntry); 807 808 List<NotificationGroup> result = mPreprocessingManager.additionalGroupAndRank(newEntry, 809 generateRankingMap(mAlertEntries), /* isUpdate= */ false); 810 assertThat(result.get(0).getSingleNotification()).isEqualTo(newEntry); 811 } 812 813 @Test onAdditionalGroupAndRank_notGroupSummary_isUpdate_notificationUpdated()814 public void onAdditionalGroupAndRank_notGroupSummary_isUpdate_notificationUpdated() { 815 when(mNotificationDataManager.isNotificationSeen(mImportantForeground)).thenReturn(false); 816 // Seed the list 817 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 818 String key = mImportantForeground.getKey(); 819 String groupKey = mImportantForeground.getStatusBarNotification().getGroupKey(); 820 Notification newNotification = generateNotification(/* isForeground= */ true, 821 /* isNavigation= */ false, /* isGroupSummary= */ false); 822 StatusBarNotification newSbn = mock(StatusBarNotification.class); 823 when(newSbn.getNotification()).thenReturn(newNotification); 824 when(newSbn.getKey()).thenReturn(key); 825 when(newSbn.getGroupKey()).thenReturn(groupKey); 826 when(newSbn.getId()).thenReturn(123); 827 AlertEntry newEntry = new AlertEntry(newSbn); 828 829 List<NotificationGroup> result = mPreprocessingManager.additionalGroupAndRank(newEntry, 830 generateRankingMap(mAlertEntries), /* isUpdate= */ true); 831 832 assertThat(result.get(0).getSingleNotification().getStatusBarNotification().getId()) 833 .isEqualTo(123); 834 } 835 836 @Test onUpdateNotifications_notificationRemoved_removesNotification()837 public void onUpdateNotifications_notificationRemoved_removesNotification() { 838 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 839 840 List<NotificationGroup> newList = 841 mPreprocessingManager.updateNotifications( 842 /* showLessImportantNotifications= */ false, 843 mImportantForeground, 844 CarNotificationListener.NOTIFY_NOTIFICATION_REMOVED, 845 mRankingMap); 846 847 assertThat(mPreprocessingManager.getOldNotifications().containsKey( 848 mImportantForeground.getKey())).isFalse(); 849 } 850 851 @Test onUpdateNotification_notificationPosted_isUpdate_putsNotification()852 public void onUpdateNotification_notificationPosted_isUpdate_putsNotification() { 853 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 854 int beforeSize = mPreprocessingManager.getOldNotifications().size(); 855 Notification newNotification = new Notification.Builder(mContext, CHANNEL_ID) 856 .setContentTitle("NEW_TITLE") 857 .setGroup(OVERRIDE_GROUP_KEY) 858 .setGroupSummary(false) 859 .build(); 860 newNotification.category = Notification.CATEGORY_NAVIGATION; 861 when(mImportantForeground.getStatusBarNotification().getNotification()) 862 .thenReturn(newNotification); 863 List<NotificationGroup> newList = 864 mPreprocessingManager.updateNotifications( 865 /* showLessImportantNotifications= */ false, 866 mImportantForeground, 867 CarNotificationListener.NOTIFY_NOTIFICATION_POSTED, 868 mRankingMap); 869 870 int afterSize = mPreprocessingManager.getOldNotifications().size(); 871 AlertEntry updated = (AlertEntry) mPreprocessingManager.getOldNotifications().get( 872 mImportantForeground.getKey()); 873 assertThat(updated).isNotNull(); 874 assertThat(updated.getNotification().category).isEqualTo(Notification.CATEGORY_NAVIGATION); 875 assertThat(afterSize).isEqualTo(beforeSize); 876 } 877 878 @Test onUpdateNotification_notificationPosted_isNotUpdate_addsNotification()879 public void onUpdateNotification_notificationPosted_isNotUpdate_addsNotification() { 880 mPreprocessingManager.init(mAlertEntriesMap, mRankingMap); 881 int beforeSize = mPreprocessingManager.getOldNotifications().size(); 882 Notification additionalNotification = generateNotification(/* isForeground= */ true, 883 /* isNavigation= */ false, /* isGroupSummary= */ true); 884 additionalNotification.category = Notification.CATEGORY_MESSAGE; 885 when(mAdditionalStatusBarNotification.getKey()).thenReturn("ADDITIONAL"); 886 when(mAdditionalStatusBarNotification.getGroupKey()).thenReturn(GROUP_KEY_C); 887 when(mAdditionalStatusBarNotification.getNotification()).thenReturn(additionalNotification); 888 AlertEntry additionalAlertEntry = new AlertEntry(mAdditionalStatusBarNotification); 889 890 List<NotificationGroup> newList = 891 mPreprocessingManager.updateNotifications( 892 /* showLessImportantNotifications= */ false, 893 additionalAlertEntry, 894 CarNotificationListener.NOTIFY_NOTIFICATION_POSTED, 895 mRankingMap); 896 897 int afterSize = mPreprocessingManager.getOldNotifications().size(); 898 AlertEntry posted = (AlertEntry) mPreprocessingManager.getOldNotifications().get( 899 additionalAlertEntry.getKey()); 900 assertThat(posted).isNotNull(); 901 assertThat(posted.getKey()).isEqualTo("ADDITIONAL"); 902 assertThat(afterSize).isEqualTo(beforeSize + 1); 903 } 904 setConfig(boolean recentOld, boolean launcherIcon, int groupThreshold)905 private void setConfig(boolean recentOld, boolean launcherIcon, int groupThreshold) { 906 TestableResources testableResources = mContext.getOrCreateTestableResources(); 907 testableResources.removeOverride(R.bool.config_showRecentAndOldHeaders); 908 testableResources.removeOverride(R.bool.config_useLauncherIcon); 909 testableResources.removeOverride(R.integer.config_minimumGroupingThreshold); 910 testableResources.addOverride(R.bool.config_showRecentAndOldHeaders, recentOld); 911 testableResources.addOverride(R.bool.config_useLauncherIcon, launcherIcon); 912 testableResources.addOverride(R.integer.config_minimumGroupingThreshold, groupThreshold); 913 } 914 915 /** 916 * Wraps StatusBarNotifications with AlertEntries and generates AlertEntriesMap and 917 * RankingsMap. 918 */ initTestData(boolean includeAdditionalNotifs)919 private void initTestData(boolean includeAdditionalNotifs) { 920 mAlertEntries = new ArrayList<>(); 921 mLessImportantBackground = new AlertEntry(mStatusBarNotification1); 922 mLessImportantForeground = new AlertEntry(mStatusBarNotification2); 923 mMedia = new AlertEntry(mStatusBarNotification3); 924 mNavigation = new AlertEntry(mStatusBarNotification4); 925 mImportantBackground = new AlertEntry(mStatusBarNotification5); 926 mImportantForeground = new AlertEntry(mStatusBarNotification6); 927 if (includeAdditionalNotifs) { 928 mImportantForeground2 = new AlertEntry(mStatusBarNotification7); 929 mImportantForeground3 = new AlertEntry(mStatusBarNotification8); 930 mImportantForeground4 = new AlertEntry(mStatusBarNotification9); 931 mImportantForeground5 = new AlertEntry(mStatusBarNotification10); 932 mImportantForeground6 = new AlertEntry(mStatusBarNotification11); 933 mImportantForeground7 = new AlertEntry(mStatusBarNotification12); 934 } 935 mAlertEntries.add(mLessImportantBackground); 936 mAlertEntries.add(mLessImportantForeground); 937 mAlertEntries.add(mMedia); 938 mAlertEntries.add(mNavigation); 939 mAlertEntries.add(mImportantBackground); 940 mAlertEntries.add(mImportantForeground); 941 if (includeAdditionalNotifs) { 942 mAlertEntries.add(mImportantForeground2); 943 mAlertEntries.add(mImportantForeground3); 944 mAlertEntries.add(mImportantForeground4); 945 mAlertEntries.add(mImportantForeground5); 946 mAlertEntries.add(mImportantForeground6); 947 mAlertEntries.add(mImportantForeground7); 948 } 949 mAlertEntriesMap = new HashMap<>(); 950 mAlertEntriesMap.put(mLessImportantBackground.getKey(), mLessImportantBackground); 951 mAlertEntriesMap.put(mLessImportantForeground.getKey(), mLessImportantForeground); 952 mAlertEntriesMap.put(mMedia.getKey(), mMedia); 953 mAlertEntriesMap.put(mNavigation.getKey(), mNavigation); 954 mAlertEntriesMap.put(mImportantBackground.getKey(), mImportantBackground); 955 mAlertEntriesMap.put(mImportantForeground.getKey(), mImportantForeground); 956 if (includeAdditionalNotifs) { 957 mAlertEntriesMap.put(mImportantForeground2.getKey(), mImportantForeground2); 958 mAlertEntriesMap.put(mImportantForeground3.getKey(), mImportantForeground3); 959 mAlertEntriesMap.put(mImportantForeground4.getKey(), mImportantForeground4); 960 mAlertEntriesMap.put(mImportantForeground5.getKey(), mImportantForeground5); 961 mAlertEntriesMap.put(mImportantForeground6.getKey(), mImportantForeground6); 962 mAlertEntriesMap.put(mImportantForeground7.getKey(), mImportantForeground7); 963 } 964 mRankingMap = generateRankingMap(mAlertEntries); 965 } 966 getEmptyAutoGeneratedGroupSummary()967 private AlertEntry getEmptyAutoGeneratedGroupSummary() { 968 Notification notification = new Notification.Builder(mContext, CHANNEL_ID) 969 .setContentTitle(CONTENT_TITLE) 970 .setSmallIcon(android.R.drawable.sym_def_app_icon) 971 .setGroup(OVERRIDE_GROUP_KEY) 972 .setGroupSummary(true) 973 .build(); 974 StatusBarNotification statusBarNotification = new StatusBarNotification( 975 PKG, OP_PKG, ID, TAG, UID, INITIAL_PID, notification, USER_HANDLE, 976 OVERRIDE_GROUP_KEY, POST_TIME); 977 statusBarNotification.setOverrideGroupKey(OVERRIDE_GROUP_KEY); 978 979 return new AlertEntry(statusBarNotification); 980 } 981 generateNotification(boolean isForeground, boolean isNavigation, boolean isGroupSummary)982 private Notification generateNotification(boolean isForeground, boolean isNavigation, 983 boolean isGroupSummary) { 984 Notification notification = new Notification.Builder(mContext, CHANNEL_ID) 985 .setContentTitle(CONTENT_TITLE) 986 .setSmallIcon(android.R.drawable.sym_def_app_icon) 987 .setGroup(OVERRIDE_GROUP_KEY) 988 .setGroupSummary(isGroupSummary) 989 .build(); 990 991 if (isForeground) { 992 notification.flags = Notification.FLAG_FOREGROUND_SERVICE; 993 } 994 995 if (isNavigation) { 996 notification.category = Notification.CATEGORY_NAVIGATION; 997 } 998 return notification; 999 } 1000 generateStringOfLength(int length)1001 private String generateStringOfLength(int length) { 1002 String string = ""; 1003 for (int i = 0; i < length; i++) { 1004 string += "*"; 1005 } 1006 1007 return string; 1008 } 1009 1010 /** 1011 * Ranks the provided alertEntries in reverse order. 1012 * 1013 * All methods that follow afterwards help assigning diverse attributes to the {@link 1014 * android.service.notification.NotificationListenerService.Ranking} instances. 1015 */ generateRankingMap( List<AlertEntry> alertEntries)1016 private NotificationListenerService.RankingMap generateRankingMap( 1017 List<AlertEntry> alertEntries) { 1018 NotificationListenerService.Ranking[] rankings = 1019 new NotificationListenerService.Ranking[alertEntries.size()]; 1020 for (int i = 0; i < alertEntries.size(); i++) { 1021 String key = alertEntries.get(i).getKey(); 1022 int rank = alertEntries.size() - i; // ranking in reverse order; 1023 NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); 1024 ranking.populate( 1025 key, 1026 rank, 1027 !isIntercepted(i), 1028 getVisibilityOverride(i), 1029 getSuppressedVisualEffects(i), 1030 getImportance(i), 1031 getExplanation(key), 1032 getOverrideGroupKey(key), 1033 getChannel(key, i), 1034 getPeople(key, i), 1035 getSnoozeCriteria(key, i), 1036 getShowBadge(i), 1037 getUserSentiment(i), 1038 getHidden(i), 1039 lastAudiblyAlerted(i), 1040 getNoisy(i), 1041 getSmartActions(key, i), 1042 getSmartReplies(key, i), 1043 canBubble(i), 1044 isVisuallyInterruptive(i), 1045 isConversation(i), 1046 null, 1047 getRankingAdjustment(i), 1048 isBubble(i) 1049 ); 1050 rankings[i] = ranking; 1051 } 1052 1053 NotificationListenerService.RankingMap rankingMap 1054 = new NotificationListenerService.RankingMap(rankings); 1055 1056 return rankingMap; 1057 } 1058 getVisibilityOverride(int index)1059 private int getVisibilityOverride(int index) { 1060 return index * 9; 1061 } 1062 getOverrideGroupKey(String key)1063 private String getOverrideGroupKey(String key) { 1064 return key + key; 1065 } 1066 isIntercepted(int index)1067 private boolean isIntercepted(int index) { 1068 return index % 2 == 0; 1069 } 1070 getSuppressedVisualEffects(int index)1071 private int getSuppressedVisualEffects(int index) { 1072 return index * 2; 1073 } 1074 getImportance(int index)1075 private int getImportance(int index) { 1076 return index; 1077 } 1078 getExplanation(String key)1079 private String getExplanation(String key) { 1080 return key + "explain"; 1081 } 1082 getChannel(String key, int index)1083 private NotificationChannel getChannel(String key, int index) { 1084 return new NotificationChannel(key, key, getImportance(index)); 1085 } 1086 getShowBadge(int index)1087 private boolean getShowBadge(int index) { 1088 return index % 3 == 0; 1089 } 1090 getUserSentiment(int index)1091 private int getUserSentiment(int index) { 1092 switch (index % 3) { 1093 case 0: 1094 return USER_SENTIMENT_NEGATIVE; 1095 case 1: 1096 return USER_SENTIMENT_NEUTRAL; 1097 case 2: 1098 return USER_SENTIMENT_POSITIVE; 1099 } 1100 return USER_SENTIMENT_NEUTRAL; 1101 } 1102 getHidden(int index)1103 private boolean getHidden(int index) { 1104 return index % 2 == 0; 1105 } 1106 lastAudiblyAlerted(int index)1107 private long lastAudiblyAlerted(int index) { 1108 return index * 2000; 1109 } 1110 getNoisy(int index)1111 private boolean getNoisy(int index) { 1112 return index < 1; 1113 } 1114 getPeople(String key, int index)1115 private ArrayList<String> getPeople(String key, int index) { 1116 ArrayList<String> people = new ArrayList<>(); 1117 for (int i = 0; i < index; i++) { 1118 people.add(i + key); 1119 } 1120 return people; 1121 } 1122 getSnoozeCriteria(String key, int index)1123 private ArrayList<SnoozeCriterion> getSnoozeCriteria(String key, int index) { 1124 ArrayList<SnoozeCriterion> snooze = new ArrayList<>(); 1125 for (int i = 0; i < index; i++) { 1126 snooze.add(new SnoozeCriterion(key + i, getExplanation(key), key)); 1127 } 1128 return snooze; 1129 } 1130 getSmartActions(String key, int index)1131 private ArrayList<Notification.Action> getSmartActions(String key, int index) { 1132 ArrayList<Notification.Action> actions = new ArrayList<>(); 1133 for (int i = 0; i < index; i++) { 1134 PendingIntent intent = PendingIntent.getBroadcast( 1135 mContext, 1136 index /*requestCode*/, 1137 new Intent("ACTION_" + key), 1138 FLAG_IMMUTABLE); 1139 actions.add(new Notification.Action.Builder(null /*icon*/, key, intent).build()); 1140 } 1141 return actions; 1142 } 1143 getSmartReplies(String key, int index)1144 private ArrayList<CharSequence> getSmartReplies(String key, int index) { 1145 ArrayList<CharSequence> choices = new ArrayList<>(); 1146 for (int i = 0; i < index; i++) { 1147 choices.add("choice_" + key + "_" + i); 1148 } 1149 return choices; 1150 } 1151 canBubble(int index)1152 private boolean canBubble(int index) { 1153 return index % 4 == 0; 1154 } 1155 isVisuallyInterruptive(int index)1156 private boolean isVisuallyInterruptive(int index) { 1157 return index % 4 == 0; 1158 } 1159 isConversation(int index)1160 private boolean isConversation(int index) { 1161 return index % 4 == 0; 1162 } 1163 getRankingAdjustment(int index)1164 private int getRankingAdjustment(int index) { 1165 return index % 3 - 1; 1166 } 1167 isBubble(int index)1168 private boolean isBubble(int index) { 1169 return index % 4 == 0; 1170 } 1171 } 1172