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