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