• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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