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