1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.service.notification; 18 19 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; 20 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; 21 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; 22 23 import static junit.framework.Assert.assertEquals; 24 import static junit.framework.Assert.assertFalse; 25 import static junit.framework.Assert.assertNotNull; 26 import static junit.framework.Assert.assertNull; 27 import static junit.framework.Assert.assertTrue; 28 29 import static org.junit.Assert.assertArrayEquals; 30 import static org.junit.Assert.assertNotEquals; 31 import static org.mockito.Mockito.spy; 32 33 import android.app.Notification; 34 import android.app.NotificationChannel; 35 import android.app.NotificationManager; 36 import android.app.PendingIntent; 37 import android.content.ComponentName; 38 import android.content.Intent; 39 import android.content.pm.ShortcutInfo; 40 import android.os.Bundle; 41 import android.os.Parcel; 42 import android.os.SharedMemory; 43 import android.platform.test.flag.junit.SetFlagsRule; 44 import android.testing.TestableContext; 45 46 import androidx.test.InstrumentationRegistry; 47 import androidx.test.filters.SmallTest; 48 49 import org.junit.Assert; 50 import org.junit.Before; 51 import org.junit.Rule; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 import org.junit.runners.Parameterized; 55 56 import java.util.ArrayList; 57 import java.util.List; 58 59 @SmallTest 60 @RunWith(Parameterized.class) 61 public class NotificationRankingUpdateTest { 62 63 private static final String NOTIFICATION_CHANNEL_ID = "test_channel_id"; 64 private static final String TEST_KEY = "key"; 65 66 private NotificationChannel mNotificationChannel; 67 68 @Rule 69 public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); 70 71 // TODO(b/284297289): remove this flag set once resolved. 72 @Parameterized.Parameters(name = "rankingUpdateAshmem={0}") getRankingUpdateAshmem()73 public static Boolean[] getRankingUpdateAshmem() { 74 return new Boolean[] { true, false }; 75 } 76 77 @Parameterized.Parameter 78 public boolean mRankingUpdateAshmem; 79 80 @Rule 81 public TestableContext mContext = 82 spy(new TestableContext(InstrumentationRegistry.getContext(), null)); 83 getContext()84 protected TestableContext getContext() { 85 return mContext; 86 } 87 88 public static String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"}; 89 90 /** 91 * Creates a NotificationRankingUpdate with prepopulated Ranking entries 92 * @param context A testable context, used for PendingIntent creation 93 * @return The NotificationRankingUpdate to be used as test data 94 */ generateUpdate(TestableContext context)95 public static NotificationRankingUpdate generateUpdate(TestableContext context) { 96 NotificationListenerService.Ranking[] rankings = 97 new NotificationListenerService.Ranking[mKeys.length]; 98 for (int i = 0; i < mKeys.length; i++) { 99 final String key = mKeys[i]; 100 NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); 101 ranking.populate( 102 key, 103 i, 104 !isIntercepted(i), 105 getVisibilityOverride(i), 106 getSuppressedVisualEffects(i), 107 getImportance(i), 108 getExplanation(key), 109 getOverrideGroupKey(key), 110 getChannel(key, i), 111 getPeople(key, i), 112 getSnoozeCriteria(key, i), 113 getShowBadge(i), 114 getUserSentiment(i), 115 getHidden(i), 116 lastAudiblyAlerted(i), 117 getNoisy(i), 118 getSmartActions(key, i, context), 119 getSmartReplies(key, i), 120 canBubble(i), 121 isTextChanged(i), 122 isConversation(i), 123 getShortcutInfo(i), 124 getRankingAdjustment(i), 125 isBubble(i), 126 getProposedImportance(i), 127 hasSensitiveContent(i) 128 ); 129 rankings[i] = ranking; 130 } 131 return new NotificationRankingUpdate(rankings); 132 } 133 134 /** 135 * Produces a visibility override value based on the provided index. 136 */ getVisibilityOverride(int index)137 public static int getVisibilityOverride(int index) { 138 return index * 9; 139 } 140 141 /** 142 * Produces a group key based on the provided key. 143 */ getOverrideGroupKey(String key)144 public static String getOverrideGroupKey(String key) { 145 return key + key; 146 } 147 148 /** 149 * Produces a boolean that can be used to represent isIntercepted, based on the provided index. 150 */ isIntercepted(int index)151 public static boolean isIntercepted(int index) { 152 return index % 2 == 0; 153 } 154 155 /** 156 * Produces a suppressed visual effects value based on the provided index 157 */ getSuppressedVisualEffects(int index)158 public static int getSuppressedVisualEffects(int index) { 159 return index * 2; 160 } 161 162 /** 163 * Produces an importance value, based on the provided index 164 */ getImportance(int index)165 public static int getImportance(int index) { 166 return index; 167 } 168 169 /** 170 * Produces an explanation value, based on the provided key 171 */ getExplanation(String key)172 public static String getExplanation(String key) { 173 return key + "explain"; 174 } 175 176 /** 177 * Produces a notification channel, based on the provided key and index 178 */ getChannel(String key, int index)179 public static NotificationChannel getChannel(String key, int index) { 180 return new NotificationChannel(key, key, getImportance(index)); 181 } 182 183 /** 184 * Produces a boolean that can be used to represent showBadge, based on the provided index 185 */ getShowBadge(int index)186 public static boolean getShowBadge(int index) { 187 return index % 3 == 0; 188 } 189 190 /** 191 * Produces a user sentiment value, based on the provided index 192 */ getUserSentiment(int index)193 public static int getUserSentiment(int index) { 194 switch(index % 3) { 195 case 0: 196 return USER_SENTIMENT_NEGATIVE; 197 case 1: 198 return USER_SENTIMENT_NEUTRAL; 199 case 2: 200 return USER_SENTIMENT_POSITIVE; 201 } 202 return USER_SENTIMENT_NEUTRAL; 203 } 204 205 /** 206 * Produces a boolean that can be used to represent "hidden," based on the provided index. 207 */ getHidden(int index)208 public static boolean getHidden(int index) { 209 return index % 2 == 0; 210 } 211 212 /** 213 * Produces a long to represent lastAudiblyAlerted based on the provided index. 214 */ lastAudiblyAlerted(int index)215 public static long lastAudiblyAlerted(int index) { 216 return index * 2000L; 217 } 218 219 /** 220 * Produces a boolean that can be used to represent "noisy," based on the provided index. 221 */ getNoisy(int index)222 public static boolean getNoisy(int index) { 223 return index < 1; 224 } 225 226 /** 227 * Produces strings that can be used to represent people, based on the provided key and index. 228 */ getPeople(String key, int index)229 public static ArrayList<String> getPeople(String key, int index) { 230 ArrayList<String> people = new ArrayList<>(); 231 for (int i = 0; i < index; i++) { 232 people.add(i + key); 233 } 234 return people; 235 } 236 237 /** 238 * Produces a number of snoozeCriteria, based on the provided key and index. 239 */ getSnoozeCriteria(String key, int index)240 public static ArrayList<SnoozeCriterion> getSnoozeCriteria(String key, int index) { 241 ArrayList<SnoozeCriterion> snooze = new ArrayList<>(); 242 for (int i = 0; i < index; i++) { 243 snooze.add(new SnoozeCriterion(key + i, getExplanation(key), key)); 244 } 245 return snooze; 246 } 247 248 /** 249 * Produces a list of Actions which can be used to represent smartActions. 250 * These actions are built from pending intents with intent titles based on the provided 251 * key, and ids based on the provided index. 252 */ getSmartActions(String key, int index, TestableContext context)253 public static ArrayList<Notification.Action> getSmartActions(String key, 254 int index, 255 TestableContext context) { 256 ArrayList<Notification.Action> actions = new ArrayList<>(); 257 for (int i = 0; i < index; i++) { 258 PendingIntent intent = PendingIntent.getBroadcast( 259 context, 260 index /*requestCode*/, 261 new Intent("ACTION_" + key), 262 PendingIntent.FLAG_IMMUTABLE /*flags*/); 263 actions.add(new Notification.Action.Builder(null /*icon*/, key, intent).build()); 264 } 265 return actions; 266 } 267 268 /** 269 * Produces index number of "smart replies," all based on the provided key and index 270 */ getSmartReplies(String key, int index)271 public static ArrayList<CharSequence> getSmartReplies(String key, int index) { 272 ArrayList<CharSequence> choices = new ArrayList<>(); 273 for (int i = 0; i < index; i++) { 274 choices.add("choice_" + key + "_" + i); 275 } 276 return choices; 277 } 278 279 /** 280 * Produces a boolean that can be used to represent canBubble, based on the provided index 281 */ canBubble(int index)282 public static boolean canBubble(int index) { 283 return index % 4 == 0; 284 } 285 286 /** 287 * Produces a boolean that can be used to represent isTextChanged, based on the provided index. 288 */ isTextChanged(int index)289 public static boolean isTextChanged(int index) { 290 return index % 4 == 0; 291 } 292 293 /** 294 * Produces a boolean that can be used to represent isConversation, based on the provided index. 295 */ isConversation(int index)296 public static boolean isConversation(int index) { 297 return index % 4 == 0; 298 } 299 300 /** 301 * Produces a ShortcutInfo value based on the provided index. 302 */ getShortcutInfo(int index)303 public static ShortcutInfo getShortcutInfo(int index) { 304 ShortcutInfo si = new ShortcutInfo( 305 index, String.valueOf(index), "packageName", new ComponentName("1", "1"), null, 306 "title", 0, "titleResName", "text", 0, "textResName", 307 "disabledMessage", 0, "disabledMessageResName", 308 null, null, 0, null, 0, 0, 309 0, "iconResName", "bitmapPath", null, 0, 310 null, null, null, null); 311 return si; 312 } 313 314 /** 315 * Produces a rankingAdjustment value, based on the provided index. 316 */ getRankingAdjustment(int index)317 public static int getRankingAdjustment(int index) { 318 return index % 3 - 1; 319 } 320 321 /** 322 * Produces a proposedImportance, based on the provided index. 323 */ getProposedImportance(int index)324 public static int getProposedImportance(int index) { 325 return index % 5 - 1; 326 } 327 328 /** 329 * Produces a boolean that can be used to represent hasSensitiveContent, based on the provided 330 * index. 331 */ hasSensitiveContent(int index)332 public static boolean hasSensitiveContent(int index) { 333 return index % 3 == 0; 334 } 335 336 /** 337 * Produces a boolean that can be used to represent isBubble, based on the provided index. 338 */ isBubble(int index)339 public static boolean isBubble(int index) { 340 return index % 4 == 0; 341 } 342 343 /** 344 * Checks that each of the pairs of actions in the two provided lists has identical titles, 345 * and that the lists have the same number of elements. 346 */ assertActionsEqual( List<Notification.Action> expecteds, List<Notification.Action> actuals)347 public void assertActionsEqual( 348 List<Notification.Action> expecteds, List<Notification.Action> actuals) { 349 Assert.assertEquals(expecteds.size(), actuals.size()); 350 for (int i = 0; i < expecteds.size(); i++) { 351 Notification.Action expected = expecteds.get(i); 352 Notification.Action actual = actuals.get(i); 353 Assert.assertEquals(expected.title.toString(), actual.title.toString()); 354 } 355 } 356 357 /** 358 * Checks that all subelements of the provided NotificationRankingUpdates are equal. 359 */ detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b)360 public void detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b) { 361 detailedAssertEquals(a.getRankingMap(), b.getRankingMap()); 362 } 363 364 /** 365 * Checks that all subelements of the provided Ranking objects are equal. 366 */ detailedAssertEquals(String comment, NotificationListenerService.Ranking a, NotificationListenerService.Ranking b)367 public void detailedAssertEquals(String comment, NotificationListenerService.Ranking a, 368 NotificationListenerService.Ranking b) { 369 Assert.assertEquals(comment, a.getKey(), b.getKey()); 370 Assert.assertEquals(comment, a.getRank(), b.getRank()); 371 Assert.assertEquals(comment, a.matchesInterruptionFilter(), b.matchesInterruptionFilter()); 372 Assert.assertEquals(comment, a.getLockscreenVisibilityOverride(), 373 b.getLockscreenVisibilityOverride()); 374 Assert.assertEquals(comment, a.getSuppressedVisualEffects(), 375 b.getSuppressedVisualEffects()); 376 Assert.assertEquals(comment, a.getImportance(), b.getImportance()); 377 Assert.assertEquals(comment, a.getImportanceExplanation(), b.getImportanceExplanation()); 378 Assert.assertEquals(comment, a.getOverrideGroupKey(), b.getOverrideGroupKey()); 379 Assert.assertEquals(comment, a.getChannel().toString(), b.getChannel().toString()); 380 Assert.assertEquals(comment, a.getAdditionalPeople(), b.getAdditionalPeople()); 381 Assert.assertEquals(comment, a.getSnoozeCriteria(), b.getSnoozeCriteria()); 382 Assert.assertEquals(comment, a.canShowBadge(), b.canShowBadge()); 383 Assert.assertEquals(comment, a.getUserSentiment(), b.getUserSentiment()); 384 Assert.assertEquals(comment, a.isSuspended(), b.isSuspended()); 385 Assert.assertEquals(comment, a.getLastAudiblyAlertedMillis(), 386 b.getLastAudiblyAlertedMillis()); 387 Assert.assertEquals(comment, a.isNoisy(), b.isNoisy()); 388 Assert.assertEquals(comment, a.getSmartReplies(), b.getSmartReplies()); 389 Assert.assertEquals(comment, a.canBubble(), b.canBubble()); 390 Assert.assertEquals(comment, a.isConversation(), b.isConversation()); 391 if (a.getConversationShortcutInfo() != null && b.getConversationShortcutInfo() != null) { 392 Assert.assertEquals(comment, a.getConversationShortcutInfo().getId(), 393 b.getConversationShortcutInfo().getId()); 394 } else { 395 // One or both must be null, so we can check for equality. 396 Assert.assertEquals(a.getConversationShortcutInfo(), b.getConversationShortcutInfo()); 397 } 398 assertActionsEqual(a.getSmartActions(), b.getSmartActions()); 399 Assert.assertEquals(a.getProposedImportance(), b.getProposedImportance()); 400 Assert.assertEquals(a.hasSensitiveContent(), b.hasSensitiveContent()); 401 } 402 403 /** 404 * Checks that the two RankingMaps have identical keys, and that each Ranking object for 405 * each of those keys is identical. 406 */ detailedAssertEquals(NotificationListenerService.RankingMap a, NotificationListenerService.RankingMap b)407 public void detailedAssertEquals(NotificationListenerService.RankingMap a, 408 NotificationListenerService.RankingMap b) { 409 NotificationListenerService.Ranking arank = new NotificationListenerService.Ranking(); 410 NotificationListenerService.Ranking brank = new NotificationListenerService.Ranking(); 411 assertArrayEquals(a.getOrderedKeys(), b.getOrderedKeys()); 412 for (String key : a.getOrderedKeys()) { 413 a.getRanking(key, arank); 414 b.getRanking(key, brank); 415 detailedAssertEquals("ranking for key <" + key + ">", arank, brank); 416 } 417 } 418 419 @Before setUp()420 public void setUp() { 421 mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel", 422 NotificationManager.IMPORTANCE_DEFAULT); 423 424 if (mRankingUpdateAshmem) { 425 mSetFlagsRule.enableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM); 426 } else { 427 mSetFlagsRule.disableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM); 428 } 429 } 430 431 /** 432 * Creates a mostly empty Test Ranking object with the specified key, rank, and smartActions. 433 */ createEmptyTestRanking( String key, int rank, ArrayList<Notification.Action> actions)434 public NotificationListenerService.Ranking createEmptyTestRanking( 435 String key, int rank, ArrayList<Notification.Action> actions) { 436 NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); 437 438 ranking.populate( 439 /* key= */ key, 440 /* rank= */ rank, 441 /* matchesInterruptionFilter= */ false, 442 /* visibilityOverride= */ 0, 443 /* suppressedVisualEffects= */ 0, 444 mNotificationChannel.getImportance(), 445 /* explanation= */ null, 446 /* overrideGroupKey= */ null, 447 mNotificationChannel, 448 /* overridePeople= */ null, 449 /* snoozeCriteria= */ null, 450 /* showBadge= */ true, 451 /* userSentiment= */ 0, 452 /* hidden= */ false, 453 /* lastAudiblyAlertedMs= */ -1, 454 /* noisy= */ false, 455 /* smartActions= */ actions, 456 /* smartReplies= */ null, 457 /* canBubble= */ false, 458 /* isTextChanged= */ false, 459 /* isConversation= */ false, 460 /* shortcutInfo= */ null, 461 /* rankingAdjustment= */ 0, 462 /* isBubble= */ false, 463 /* proposedImportance= */ 0, 464 /* sensitiveContent= */ false 465 ); 466 return ranking; 467 } 468 469 // Tests parceling of NotificationRankingUpdate, and by extension, RankingMap and Ranking. 470 @Test testRankingUpdate_parcel()471 public void testRankingUpdate_parcel() { 472 NotificationRankingUpdate nru = generateUpdate(getContext()); 473 Parcel parcel = Parcel.obtain(); 474 nru.writeToParcel(parcel, 0); 475 if (Flags.rankingUpdateAshmem()) { 476 assertTrue(nru.isFdNotNullAndClosed()); 477 } 478 parcel.setDataPosition(0); 479 NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel); 480 // The rankingUpdate file descriptor is only non-null in the new path. 481 if (Flags.rankingUpdateAshmem()) { 482 assertTrue(nru1.isFdNotNullAndClosed()); 483 } 484 detailedAssertEquals(nru, nru1); 485 parcel.recycle(); 486 } 487 488 // Tests parceling of RankingMap and RankingMap.equals 489 @Test testRankingMap_parcel()490 public void testRankingMap_parcel() { 491 NotificationListenerService.RankingMap rmap = generateUpdate(getContext()).getRankingMap(); 492 Parcel parcel = Parcel.obtain(); 493 rmap.writeToParcel(parcel, 0); 494 parcel.setDataPosition(0); 495 NotificationListenerService.RankingMap rmap1 = 496 NotificationListenerService.RankingMap.CREATOR.createFromParcel(parcel); 497 498 detailedAssertEquals(rmap, rmap1); 499 Assert.assertEquals(rmap, rmap1); 500 parcel.recycle(); 501 } 502 503 // Tests parceling of Ranking and Ranking.equals 504 @Test testRanking_parcel()505 public void testRanking_parcel() { 506 NotificationListenerService.Ranking ranking = 507 generateUpdate(getContext()).getRankingMap().getRawRankingObject(mKeys[0]); 508 Parcel parcel = Parcel.obtain(); 509 ranking.writeToParcel(parcel, 0); 510 parcel.setDataPosition(0); 511 NotificationListenerService.Ranking ranking1 = 512 new NotificationListenerService.Ranking(parcel); 513 detailedAssertEquals("rankings differ: ", ranking, ranking1); 514 Assert.assertEquals(ranking, ranking1); 515 parcel.recycle(); 516 } 517 518 // Tests NotificationRankingUpdate.equals(), and by extension, RankingMap and Ranking. 519 @Test testRankingUpdate_equals_legacy()520 public void testRankingUpdate_equals_legacy() { 521 NotificationRankingUpdate nru = generateUpdate(getContext()); 522 NotificationRankingUpdate nru2 = generateUpdate(getContext()); 523 detailedAssertEquals(nru, nru2); 524 Assert.assertEquals(nru, nru2); 525 NotificationListenerService.Ranking tweak = 526 nru2.getRankingMap().getRawRankingObject(mKeys[0]); 527 tweak.populate( 528 tweak.getKey(), 529 tweak.getRank(), 530 !tweak.matchesInterruptionFilter(), // note the inversion here! 531 tweak.getLockscreenVisibilityOverride(), 532 tweak.getSuppressedVisualEffects(), 533 tweak.getImportance(), 534 tweak.getImportanceExplanation(), 535 tweak.getOverrideGroupKey(), 536 tweak.getChannel(), 537 (ArrayList) tweak.getAdditionalPeople(), 538 (ArrayList) tweak.getSnoozeCriteria(), 539 tweak.canShowBadge(), 540 tweak.getUserSentiment(), 541 tweak.isSuspended(), 542 tweak.getLastAudiblyAlertedMillis(), 543 tweak.isNoisy(), 544 (ArrayList) tweak.getSmartActions(), 545 (ArrayList) tweak.getSmartReplies(), 546 tweak.canBubble(), 547 tweak.isTextChanged(), 548 tweak.isConversation(), 549 tweak.getConversationShortcutInfo(), 550 tweak.getRankingAdjustment(), 551 tweak.isBubble(), 552 tweak.getProposedImportance(), 553 tweak.hasSensitiveContent() 554 ); 555 assertNotEquals(nru, nru2); 556 } 557 558 @Test testRankingUpdate_rankingConstructor()559 public void testRankingUpdate_rankingConstructor() { 560 NotificationRankingUpdate nru = generateUpdate(getContext()); 561 NotificationRankingUpdate constructedNru = new NotificationRankingUpdate( 562 new NotificationListenerService.Ranking[]{ 563 nru.getRankingMap().getRawRankingObject(mKeys[0]), 564 nru.getRankingMap().getRawRankingObject(mKeys[1]), 565 nru.getRankingMap().getRawRankingObject(mKeys[2]), 566 nru.getRankingMap().getRawRankingObject(mKeys[3]), 567 nru.getRankingMap().getRawRankingObject(mKeys[4]) 568 }); 569 570 detailedAssertEquals(nru, constructedNru); 571 } 572 573 @Test testRankingUpdate_emptyParcelInCheck()574 public void testRankingUpdate_emptyParcelInCheck() { 575 NotificationRankingUpdate rankingUpdate = generateUpdate(getContext()); 576 Parcel parceledRankingUpdate = Parcel.obtain(); 577 rankingUpdate.writeToParcel(parceledRankingUpdate, 0); 578 579 // This will fail to read the parceledRankingUpdate, because the data position hasn't 580 // been reset, so it'll find no data to read. 581 NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate( 582 parceledRankingUpdate); 583 assertNull(retrievedRankingUpdate.getRankingMap()); 584 parceledRankingUpdate.recycle(); 585 } 586 587 @Test testRankingUpdate_describeContents()588 public void testRankingUpdate_describeContents() { 589 NotificationRankingUpdate rankingUpdate = generateUpdate(getContext()); 590 assertEquals(0, rankingUpdate.describeContents()); 591 } 592 593 @Test testRankingUpdate_equals()594 public void testRankingUpdate_equals() { 595 NotificationListenerService.Ranking ranking = createEmptyTestRanking(TEST_KEY, 123, null); 596 NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( 597 new NotificationListenerService.Ranking[]{ranking}); 598 // Reflexive equality, including handling nulls properly 599 detailedAssertEquals(rankingUpdate, rankingUpdate); 600 // Null or wrong class inequality 601 assertFalse(rankingUpdate.equals(null)); 602 assertFalse(rankingUpdate.equals(ranking)); 603 604 // Different rank inequality 605 NotificationListenerService.Ranking ranking2 = createEmptyTestRanking(TEST_KEY, 456, null); 606 NotificationRankingUpdate rankingUpdate2 = new NotificationRankingUpdate( 607 new NotificationListenerService.Ranking[]{ranking2}); 608 assertFalse(rankingUpdate.equals(rankingUpdate2)); 609 610 // Different key inequality 611 ranking2 = createEmptyTestRanking(TEST_KEY + "DIFFERENT", 123, null); 612 rankingUpdate2 = new NotificationRankingUpdate( 613 new NotificationListenerService.Ranking[]{ranking2}); 614 assertFalse(rankingUpdate.equals(rankingUpdate2)); 615 } 616 617 @Test testRankingUpdate_writesSmartActionToParcel()618 public void testRankingUpdate_writesSmartActionToParcel() { 619 if (!Flags.rankingUpdateAshmem()) { 620 return; 621 } 622 ArrayList<Notification.Action> actions = new ArrayList<>(); 623 PendingIntent intent = PendingIntent.getBroadcast( 624 getContext(), 625 0 /*requestCode*/, 626 new Intent("ACTION_" + TEST_KEY), 627 PendingIntent.FLAG_IMMUTABLE /*flags*/); 628 actions.add(new Notification.Action.Builder(null /*icon*/, TEST_KEY, intent).build()); 629 630 NotificationListenerService.Ranking ranking = 631 createEmptyTestRanking(TEST_KEY, 123, actions); 632 NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( 633 new NotificationListenerService.Ranking[]{ranking}); 634 635 Parcel parcel = Parcel.obtain(); 636 rankingUpdate.writeToParcel(parcel, 0); 637 parcel.setDataPosition(0); 638 SharedMemory fd = parcel.readParcelable(getClass().getClassLoader(), SharedMemory.class); 639 Bundle smartActionsBundle = parcel.readBundle(getClass().getClassLoader()); 640 641 // Assert the file descriptor is valid 642 assertNotNull(fd); 643 assertFalse(fd.getFd() == -1); 644 645 // Assert that the smart action is in the parcel 646 assertNotNull(smartActionsBundle); 647 ArrayList<Notification.Action> recoveredActions = 648 smartActionsBundle.getParcelableArrayList(TEST_KEY, Notification.Action.class); 649 assertNotNull(recoveredActions); 650 assertEquals(actions.size(), recoveredActions.size()); 651 assertEquals(actions.get(0).title.toString(), recoveredActions.get(0).title.toString()); 652 parcel.recycle(); 653 } 654 655 @Test testRankingUpdate_handlesEmptySmartActionList()656 public void testRankingUpdate_handlesEmptySmartActionList() { 657 if (!Flags.rankingUpdateAshmem()) { 658 return; 659 } 660 ArrayList<Notification.Action> actions = new ArrayList<>(); 661 NotificationListenerService.Ranking ranking = 662 createEmptyTestRanking(TEST_KEY, 123, actions); 663 NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( 664 new NotificationListenerService.Ranking[]{ranking}); 665 666 Parcel parcel = Parcel.obtain(); 667 rankingUpdate.writeToParcel(parcel, 0); 668 parcel.setDataPosition(0); 669 670 // Ensure that despite an empty actions list, we can still unparcel the update. 671 NotificationRankingUpdate newRankingUpdate = new NotificationRankingUpdate(parcel); 672 assertNotNull(newRankingUpdate); 673 assertNotNull(newRankingUpdate.getRankingMap()); 674 detailedAssertEquals(rankingUpdate, newRankingUpdate); 675 parcel.recycle(); 676 } 677 678 @Test testRankingUpdate_handlesNullSmartActionList()679 public void testRankingUpdate_handlesNullSmartActionList() { 680 if (!Flags.rankingUpdateAshmem()) { 681 return; 682 } 683 NotificationListenerService.Ranking ranking = 684 createEmptyTestRanking(TEST_KEY, 123, null); 685 NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate( 686 new NotificationListenerService.Ranking[]{ranking}); 687 688 Parcel parcel = Parcel.obtain(); 689 rankingUpdate.writeToParcel(parcel, 0); 690 parcel.setDataPosition(0); 691 692 // Ensure that despite an empty actions list, we can still unparcel the update. 693 NotificationRankingUpdate newRankingUpdate = new NotificationRankingUpdate(parcel); 694 assertNotNull(newRankingUpdate); 695 assertNotNull(newRankingUpdate.getRankingMap()); 696 detailedAssertEquals(rankingUpdate, newRankingUpdate); 697 parcel.recycle(); 698 } 699 } 700