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