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