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