• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 package com.android.server.notification;
17 
18 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
19 import static android.app.NotificationManager.IMPORTANCE_LOW;
20 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static junit.framework.TestCase.assertEquals;
25 
26 import static org.junit.Assert.assertTrue;
27 import static org.mockito.ArgumentMatchers.any;
28 import static org.mockito.ArgumentMatchers.anyInt;
29 import static org.mockito.ArgumentMatchers.eq;
30 import static org.mockito.Mockito.mock;
31 import static org.mockito.Mockito.when;
32 
33 import android.app.Notification;
34 import android.app.NotificationChannel;
35 import android.app.NotificationManager;
36 import android.content.ContentProvider;
37 import android.content.Context;
38 import android.content.IContentProvider;
39 import android.content.pm.ApplicationInfo;
40 import android.content.pm.PackageInfo;
41 import android.content.pm.PackageManager;
42 import android.content.pm.Signature;
43 import android.net.Uri;
44 import android.os.Build;
45 import android.os.UserHandle;
46 import android.os.Vibrator;
47 import android.platform.test.annotations.DisableFlags;
48 import android.platform.test.annotations.EnableFlags;
49 import android.platform.test.flag.junit.SetFlagsRule;
50 import android.service.notification.StatusBarNotification;
51 import android.testing.TestableContentResolver;
52 
53 import androidx.test.InstrumentationRegistry;
54 import androidx.test.filters.SmallTest;
55 import androidx.test.runner.AndroidJUnit4;
56 
57 import com.android.internal.compat.IPlatformCompat;
58 import com.android.server.UiServiceTestCase;
59 
60 import org.junit.Before;
61 import org.junit.Rule;
62 import org.junit.Test;
63 import org.junit.runner.RunWith;
64 import org.mockito.Mock;
65 import org.mockito.MockitoAnnotations;
66 
67 import java.util.ArrayList;
68 import java.util.Collections;
69 
70 @SmallTest
71 @RunWith(AndroidJUnit4.class)
72 public class RankingHelperTest extends UiServiceTestCase {
73     private static final String UPDATED_PKG = "updatedmPkg";
74     private static final int UID2 = 1111;
75     private static final String SYSTEM_PKG = "android";
76     private static final int SYSTEM_UID= 1000;
77     private static final String TEST_CHANNEL_ID = "test_channel_id";
78     private static final String TEST_AUTHORITY = "test";
79     private static final Uri SOUND_URI =
80             Uri.parse("content://" + TEST_AUTHORITY + "/internal/audio/media/10");
81     private static final Uri CANONICAL_SOUND_URI =
82             Uri.parse("content://" + TEST_AUTHORITY
83                     + "/internal/audio/media/10?title=Test&canonical=1");
84 
85     @Mock NotificationUsageStats mUsageStats;
86     @Mock RankingHandler mHandler;
87     @Mock PackageManager mPm;
88     @Mock IContentProvider mTestIContentProvider;
89     @Mock Context mContext;
90     @Mock ZenModeHelper mMockZenModeHelper;
91     @Mock RankingConfig mConfig;
92     @Mock Vibrator mVibrator;
93     @Mock GroupHelper mGroupHelper;
94 
95     private NotificationManager.Policy mTestNotificationPolicy;
96     private Notification mNotiGroupGSortA;
97     private Notification mNotiGroupGSortB;
98     private Notification mNotiNoGroup;
99     private Notification mNotiNoGroup2;
100     private Notification mNotiNoGroupSortA;
101     private NotificationRecord mRecordGroupGSortA;
102     private NotificationRecord mRecordGroupGSortB;
103     private NotificationRecord mRecordNoGroup;
104     private NotificationRecord mRecordNoGroup2;
105     private NotificationRecord mRecordNoGroupSortA;
106     private NotificationRecord mRecentlyIntrusive;
107     private NotificationRecord mNewest;
108     private RankingHelper mHelper;
109 
110     @Rule
111     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
112 
113     @Before
setUp()114     public void setUp() throws Exception {
115         MockitoAnnotations.initMocks(this);
116         UserHandle mUser = UserHandle.ALL;
117 
118         final ApplicationInfo legacy = new ApplicationInfo();
119         legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
120         final ApplicationInfo upgrade = new ApplicationInfo();
121         upgrade.targetSdkVersion = Build.VERSION_CODES.O;
122         when(mPm.getApplicationInfoAsUser(eq(mPkg), anyInt(), anyInt())).thenReturn(legacy);
123         when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
124         when(mPm.getApplicationInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(upgrade);
125         when(mPm.getPackageUidAsUser(eq(mPkg), anyInt())).thenReturn(mUid);
126         when(mPm.getPackageUidAsUser(eq(UPDATED_PKG), anyInt())).thenReturn(UID2);
127         when(mPm.getPackageUidAsUser(eq(SYSTEM_PKG), anyInt())).thenReturn(SYSTEM_UID);
128         PackageInfo info = mock(PackageInfo.class);
129         info.signatures = new Signature[] {mock(Signature.class)};
130         when(mPm.getPackageInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(info);
131         when(mPm.getPackageInfoAsUser(eq(mPkg), anyInt(), anyInt()))
132                 .thenReturn(mock(PackageInfo.class));
133         when(mContext.getResources()).thenReturn(
134                 InstrumentationRegistry.getContext().getResources());
135         when(mContext.getContentResolver()).thenReturn(
136                 InstrumentationRegistry.getContext().getContentResolver());
137         when(mContext.getPackageManager()).thenReturn(mPm);
138         when(mContext.getApplicationInfo()).thenReturn(legacy);
139         when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator);
140         TestableContentResolver contentResolver = getContext().getContentResolver();
141         contentResolver.setFallbackToExisting(false);
142 
143         ContentProvider testContentProvider = mock(ContentProvider.class);
144         when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
145         contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
146 
147         when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
148                 .thenReturn(CANONICAL_SOUND_URI);
149         when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
150                 .thenReturn(CANONICAL_SOUND_URI);
151         when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
152                 .thenReturn(SOUND_URI);
153 
154         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
155                 NotificationManager.Policy.STATE_HAS_PRIORITY_CHANNELS, 0);
156         when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
157         mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
158                 mUsageStats, new String[] {ImportanceExtractor.class.getName()},
159                 mock(IPlatformCompat.class), mGroupHelper);
160 
161         mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
162                 .setContentTitle("A")
163                 .setGroup("G")
164                 .setSortKey("A")
165                 .setWhen(1205)
166                 .build();
167         mRecordGroupGSortA = new NotificationRecord(mContext, new StatusBarNotification(
168                 mPkg, mPkg, 1, null, 0, 0, mNotiGroupGSortA, mUser,
169                 null, System.currentTimeMillis()), getLowChannel());
170 
171         mNotiGroupGSortB = new Notification.Builder(mContext, TEST_CHANNEL_ID)
172                 .setContentTitle("B")
173                 .setGroup("G")
174                 .setSortKey("B")
175                 .setWhen(1200)
176                 .build();
177         mRecordGroupGSortB = new NotificationRecord(mContext, new StatusBarNotification(
178                 mPkg, mPkg, 1, null, 0, 0, mNotiGroupGSortB, mUser,
179                 null, System.currentTimeMillis()), getLowChannel());
180 
181         mNotiNoGroup = new Notification.Builder(mContext, TEST_CHANNEL_ID)
182                 .setContentTitle("C")
183                 .setWhen(1201)
184                 .build();
185         mRecordNoGroup = new NotificationRecord(mContext, new StatusBarNotification(
186                 mPkg, mPkg, 1, null, 0, 0, mNotiNoGroup, mUser,
187                 null, System.currentTimeMillis()), getLowChannel());
188 
189         mNotiNoGroup2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
190                 .setContentTitle("D")
191                 .setWhen(1202)
192                 .build();
193         mRecordNoGroup2 = new NotificationRecord(mContext, new StatusBarNotification(
194                 mPkg, mPkg, 1, null, 0, 0, mNotiNoGroup2, mUser,
195                 null, System.currentTimeMillis()), getLowChannel());
196 
197         mNotiNoGroupSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
198                 .setContentTitle("E")
199                 .setWhen(1201)
200                 .setSortKey("A")
201                 .build();
202         mRecordNoGroupSortA = new NotificationRecord(mContext, new StatusBarNotification(
203                 mPkg, mPkg, 1, null, 0, 0, mNotiNoGroupSortA, mUser,
204                 null, System.currentTimeMillis()), getLowChannel());
205 
206         Notification n = new Notification.Builder(mContext, TEST_CHANNEL_ID)
207                 .setContentTitle("D")
208                 .build();
209         mRecentlyIntrusive = new NotificationRecord(mContext, new StatusBarNotification(
210                 mPkg, mPkg, 1, null, 0, 0, n, mUser,
211                 null, 100), getDefaultChannel());
212         mRecentlyIntrusive.setRecentlyIntrusive(true);
213 
214         mNewest = new NotificationRecord(mContext, new StatusBarNotification(
215                 mPkg, mPkg, 2, null, 0, 0, n, mUser,
216                 null, 10000), getDefaultChannel());
217     }
218 
getLowChannel()219     private NotificationChannel getLowChannel() {
220         return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
221                 IMPORTANCE_LOW);
222     }
223 
getDefaultChannel()224     private NotificationChannel getDefaultChannel() {
225         return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
226                 IMPORTANCE_DEFAULT);
227     }
228 
229     @Test
testSortShouldRespectCritical()230     public void testSortShouldRespectCritical() throws Exception {
231         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(7);
232         NotificationRecord critical = generateRecord(0);
233         NotificationRecord critical_ish = generateRecord(1);
234         NotificationRecord critical_notAtAll = generateRecord(100);
235 
236         notificationList.add(critical_ish);
237         notificationList.add(mRecordGroupGSortA);
238         notificationList.add(critical_notAtAll);
239         notificationList.add(mRecordGroupGSortB);
240         notificationList.add(mRecordNoGroup);
241         notificationList.add(mRecordNoGroupSortA);
242         notificationList.add(critical);
243         mHelper.sort(notificationList);
244 
245         assertTrue(mHelper.indexOf(notificationList, critical) == 0);
246         assertTrue(mHelper.indexOf(notificationList, critical_ish) == 1);
247         assertTrue(mHelper.indexOf(notificationList, critical_notAtAll) == 6);
248     }
249 
generateRecord(int criticality)250     private NotificationRecord generateRecord(int criticality) {
251         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
252         final Notification.Builder builder = new Notification.Builder(getContext())
253                 .setContentTitle("foo")
254                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
255         Notification n = builder.build();
256         StatusBarNotification sbn = new StatusBarNotification("", "", 0, "", 0,
257                 0, n, UserHandle.ALL, null, System.currentTimeMillis());
258         NotificationRecord notificationRecord = new NotificationRecord(getContext(), sbn, channel);
259         notificationRecord.setCriticality(criticality);
260         return notificationRecord;
261     }
262 
263     @Test
testFindAfterRankingWithASplitGroup()264     public void testFindAfterRankingWithASplitGroup() throws Exception {
265         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(4);
266         notificationList.add(mRecordGroupGSortA);
267         notificationList.add(mRecordGroupGSortB);
268         notificationList.add(mRecordNoGroup);
269         notificationList.add(mRecordNoGroupSortA);
270         mHelper.sort(notificationList);
271         assertTrue(mHelper.indexOf(notificationList, mRecordGroupGSortA) >= 0);
272         assertTrue(mHelper.indexOf(notificationList, mRecordGroupGSortB) >= 0);
273         assertTrue(mHelper.indexOf(notificationList, mRecordNoGroup) >= 0);
274         assertTrue(mHelper.indexOf(notificationList, mRecordNoGroupSortA) >= 0);
275     }
276 
277     @Test
testSortShouldNotThrowWithPlainNotifications()278     public void testSortShouldNotThrowWithPlainNotifications() throws Exception {
279         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(2);
280         notificationList.add(mRecordNoGroup);
281         notificationList.add(mRecordNoGroup2);
282         mHelper.sort(notificationList);
283     }
284 
285     @Test
testSortShouldNotThrowOneSorted()286     public void testSortShouldNotThrowOneSorted() throws Exception {
287         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(2);
288         notificationList.add(mRecordNoGroup);
289         notificationList.add(mRecordNoGroupSortA);
290         mHelper.sort(notificationList);
291     }
292 
293     @Test
testSortShouldNotThrowOneNotification()294     public void testSortShouldNotThrowOneNotification() throws Exception {
295         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(1);
296         notificationList.add(mRecordNoGroup);
297         mHelper.sort(notificationList);
298     }
299 
300     @Test
testSortShouldNotThrowOneSortKey()301     public void testSortShouldNotThrowOneSortKey() throws Exception {
302         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(1);
303         notificationList.add(mRecordGroupGSortB);
304         mHelper.sort(notificationList);
305     }
306 
307     @Test
testSortShouldNotThrowOnEmptyList()308     public void testSortShouldNotThrowOnEmptyList() throws Exception {
309         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>();
310         mHelper.sort(notificationList);
311     }
312 
313     @Test
testGroupNotifications_highestIsProxy()314     public void testGroupNotifications_highestIsProxy() {
315         ArrayList<NotificationRecord> notificationList = new ArrayList<>();
316         // this should be the last in the list, except it's in a group with a high child
317         Notification lowSummaryN = new Notification.Builder(mContext, "")
318                 .setGroup("group")
319                 .setGroupSummary(true)
320                 .build();
321         NotificationRecord lowSummary = new NotificationRecord(mContext, new StatusBarNotification(
322                 mPkg, mPkg, 1, "summary", 0, 0, lowSummaryN, mUser,
323                 null, System.currentTimeMillis()), getLowChannel());
324         notificationList.add(lowSummary);
325 
326         Notification lowN = new Notification.Builder(mContext, "").build();
327         NotificationRecord low = new NotificationRecord(mContext, new StatusBarNotification(
328                 mPkg, mPkg, 1, "low", 0, 0, lowN, mUser,
329                 null, System.currentTimeMillis()), getLowChannel());
330         low.setContactAffinity(0.5f);
331         notificationList.add(low);
332 
333         Notification highChildN = new Notification.Builder(mContext, "")
334                 .setGroup("group")
335                 .setGroupSummary(false)
336                 .build();
337         NotificationRecord highChild = new NotificationRecord(mContext, new StatusBarNotification(
338                 mPkg, mPkg, 1, "child", 0, 0, highChildN, mUser,
339                 null, System.currentTimeMillis()), getDefaultChannel());
340         notificationList.add(highChild);
341 
342         mHelper.sort(notificationList);
343 
344         assertEquals(lowSummary, notificationList.get(0));
345         assertEquals(highChild, notificationList.get(1));
346         assertEquals(low, notificationList.get(2));
347     }
348 
349     @Test
350     @DisableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME})
testSortByIntrusivenessNotRecency()351     public void testSortByIntrusivenessNotRecency() {
352         ArrayList<NotificationRecord> expected = new ArrayList<>();
353         expected.add(mRecentlyIntrusive);
354         expected.add(mNewest);
355 
356         ArrayList<NotificationRecord> actual = new ArrayList<>();
357         actual.addAll(expected);
358         Collections.shuffle(actual);
359 
360         mHelper.sort(actual);
361         assertThat(actual).containsExactlyElementsIn(expected).inOrder();
362     }
363 
364     @Test
365     @EnableFlags({android.app.Flags.FLAG_SORT_SECTION_BY_TIME})
testSortByRecencyNotIntrusiveness()366     public void testSortByRecencyNotIntrusiveness() {
367         ArrayList<NotificationRecord> expected = new ArrayList<>();
368         expected.add(mNewest);
369         expected.add(mRecentlyIntrusive);
370 
371         ArrayList<NotificationRecord> actual = new ArrayList<>();
372         actual.addAll(expected);
373         Collections.shuffle(actual);
374 
375         mHelper.sort(actual);
376         assertThat(actual).containsExactlyElementsIn(expected).inOrder();
377     }
378 
379     @Test
380     @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
testSort_oldWhenChildren_unspecifiedSummary()381     public void testSort_oldWhenChildren_unspecifiedSummary() {
382         NotificationRecord child1 = new NotificationRecord(mContext,
383                 new StatusBarNotification(
384                     mPkg, mPkg, 1, null, 0, 0,
385                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
386                             .setGroup("G")
387                             .setWhen(1200)
388                             .build(),
389                         mUser, null, System.currentTimeMillis()), getLowChannel());
390         NotificationRecord child2 = new NotificationRecord(mContext,
391                 new StatusBarNotification(
392                         mPkg, mPkg, 2, null, 0, 0,
393                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
394                                 .setGroup("G")
395                                 .setWhen(1300)
396                                 .build(),
397                         mUser, null, System.currentTimeMillis()), getLowChannel());
398         NotificationRecord summary = new NotificationRecord(mContext,
399                 new StatusBarNotification(
400                         mPkg, mPkg, 3, null, 0, 0,
401                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
402                                 .setGroup("G")
403                                 .setGroupSummary(true)
404                                 .build(),
405                         mUser, null, System.currentTimeMillis()), getLowChannel());
406 
407         // in time slightly before the children, but much earlier than the summary.
408         // will only be sorted first if the summary is not the group proxy for group G.
409         NotificationRecord unrelated = new NotificationRecord(mContext,
410                 new StatusBarNotification(
411                         mPkg, mPkg, 11, null, 0, 0,
412                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
413                                 .setWhen(1500)
414                                 .build(),
415                         mUser, null, System.currentTimeMillis()), getLowChannel());
416 
417         ArrayList<NotificationRecord> expected = new ArrayList<>();
418         expected.add(unrelated);
419         expected.add(summary);
420         expected.add(child2);
421         expected.add(child1);
422 
423         ArrayList<NotificationRecord> actual = new ArrayList<>();
424         actual.addAll(expected);
425         Collections.shuffle(actual);
426 
427         mHelper.sort(actual);
428         assertThat(actual).containsExactlyElementsIn(expected).inOrder();
429     }
430 
431     @Test
432     @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
testSort_oldChildren_unspecifiedSummary()433     public void testSort_oldChildren_unspecifiedSummary() {
434         NotificationRecord child1 = new NotificationRecord(mContext,
435                 new StatusBarNotification(
436                         mPkg, mPkg, 1, null, 0, 0,
437                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
438                                 .setGroup("G")
439                                 .build(),
440                         mUser, null, 1200), getLowChannel());
441         NotificationRecord child2 = new NotificationRecord(mContext,
442                 new StatusBarNotification(
443                         mPkg, mPkg, 2, null, 0, 0,
444                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
445                                 .setGroup("G")
446                                 .build(),
447                         mUser, null, 1300), getLowChannel());
448         NotificationRecord summary = new NotificationRecord(mContext,
449                 new StatusBarNotification(
450                         mPkg, mPkg, 3, null, 0, 0,
451                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
452                                 .setGroup("G")
453                                 .setGroupSummary(true)
454                                 .build(),
455                         mUser, null, System.currentTimeMillis()), getLowChannel());
456 
457         // in time slightly before the children, but much earlier than the summary.
458         // will only be sorted first if the summary is not the group proxy for group G.
459         NotificationRecord unrelated = new NotificationRecord(mContext,
460                 new StatusBarNotification(
461                         mPkg, mPkg, 11, null, 0, 0,
462                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
463                                 .setWhen(1500)
464                                 .build(),
465                         mUser, null, System.currentTimeMillis()), getLowChannel());
466 
467         ArrayList<NotificationRecord> expected = new ArrayList<>();
468         expected.add(unrelated);
469         expected.add(summary);
470         expected.add(child2);
471         expected.add(child1);
472 
473         ArrayList<NotificationRecord> actual = new ArrayList<>();
474         actual.addAll(expected);
475         Collections.shuffle(actual);
476 
477         mHelper.sort(actual);
478         assertThat(actual).containsExactlyElementsIn(expected).inOrder();
479     }
480 
481     @Test
482     @EnableFlags(android.app.Flags.FLAG_SORT_SECTION_BY_TIME)
testSort_oldChildren_oldSummary()483     public void testSort_oldChildren_oldSummary() {
484         NotificationRecord child1 = new NotificationRecord(mContext,
485                 new StatusBarNotification(
486                         mPkg, mPkg, 1, null, 0, 0,
487                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
488                                 .setGroup("G")
489                                 .build(),
490                         mUser, null, 1200), getLowChannel());
491         NotificationRecord child2 = new NotificationRecord(mContext,
492                 new StatusBarNotification(
493                         mPkg, mPkg, 2, null, 0, 0,
494                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
495                                 .setGroup("G")
496                                 .build(),
497                         mUser, null, 1300), getLowChannel());
498         NotificationRecord summary = new NotificationRecord(mContext,
499                 new StatusBarNotification(
500                         mPkg, mPkg, 3, null, 0, 0,
501                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
502                                 .setGroup("G")
503                                 .setGroupSummary(true)
504                                 .setWhen(1600)
505                                 .build(),
506                         mUser, null, System.currentTimeMillis()), getLowChannel());
507 
508         // in time slightly before the children, but much earlier than the summary.
509         // will only be sorted first if the summary is not the group proxy for group G.
510         NotificationRecord unrelated = new NotificationRecord(mContext,
511                 new StatusBarNotification(
512                         mPkg, mPkg, 11, null, 0, 0,
513                         new Notification.Builder(mContext, TEST_CHANNEL_ID)
514                                 .setWhen(1500)
515                                 .build(),
516                         mUser, null, System.currentTimeMillis()), getLowChannel());
517 
518         ArrayList<NotificationRecord> expected = new ArrayList<>();
519         expected.add(unrelated);
520         expected.add(summary);
521         expected.add(child2);
522         expected.add(child1);
523 
524         ArrayList<NotificationRecord> actual = new ArrayList<>();
525         actual.addAll(expected);
526         Collections.shuffle(actual);
527 
528         mHelper.sort(actual);
529         assertThat(actual).containsExactlyElementsIn(expected).inOrder();
530     }
531 }
532