1 /* 2 * Copyright (C) 2013 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 com.android.cts.verifier.notifications; 18 19 import static android.app.NotificationManager.IMPORTANCE_LOW; 20 import static android.app.NotificationManager.IMPORTANCE_MAX; 21 import static android.app.NotificationManager.IMPORTANCE_NONE; 22 import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS; 23 import static android.provider.Settings.EXTRA_APP_PACKAGE; 24 import static android.provider.Settings.EXTRA_CHANNEL_ID; 25 26 import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS; 27 import static com.android.cts.verifier.notifications.MockListener.JSON_ICON; 28 import static com.android.cts.verifier.notifications.MockListener.JSON_ID; 29 import static com.android.cts.verifier.notifications.MockListener.JSON_LAST_AUDIBLY_ALERTED; 30 import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE; 31 import static com.android.cts.verifier.notifications.MockListener.JSON_REASON; 32 import static com.android.cts.verifier.notifications.MockListener.JSON_STATS; 33 import static com.android.cts.verifier.notifications.MockListener.JSON_TAG; 34 import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN; 35 import static com.android.cts.verifier.notifications.MockListener.REASON_LISTENER_CANCEL; 36 37 import android.annotation.SuppressLint; 38 import android.app.ActivityManager; 39 import android.app.Notification; 40 import android.app.NotificationChannel; 41 import android.app.NotificationChannelGroup; 42 import android.content.Context; 43 import android.content.Intent; 44 import android.content.SharedPreferences; 45 import android.content.pm.PackageManager; 46 import android.os.Bundle; 47 import android.provider.Settings; 48 import android.provider.Settings.Secure; 49 import android.service.notification.StatusBarNotification; 50 import android.util.Log; 51 import android.view.View; 52 import android.view.ViewGroup; 53 import android.widget.Button; 54 55 import androidx.core.app.NotificationCompat; 56 57 import com.android.cts.verifier.R; 58 59 import org.json.JSONException; 60 import org.json.JSONObject; 61 62 import java.util.ArrayList; 63 import java.util.HashSet; 64 import java.util.List; 65 import java.util.Set; 66 import java.util.UUID; 67 68 public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity 69 implements Runnable { 70 private static final String TAG = "NoListenerVerifier"; 71 private static final String NOTIFICATION_CHANNEL_ID = TAG; 72 private static final String NOISY_NOTIFICATION_CHANNEL_ID = TAG + "Noisy"; 73 protected static final String PREFS = "listener_prefs"; 74 final int NUM_NOTIFICATIONS_SENT = 3; // # notifications sent by sendNotifications() 75 76 private String mTag1; 77 private String mTag2; 78 private String mTag3; 79 private String mTag4; 80 private int mIcon1; 81 private int mIcon2; 82 private int mIcon3; 83 private int mIcon4; 84 private int mId1; 85 private int mId2; 86 private int mId3; 87 private int mId4; 88 private long mWhen1; 89 private long mWhen2; 90 private long mWhen3; 91 private long mWhen4; 92 private int mFlag1; 93 private int mFlag2; 94 private int mFlag3; 95 96 @Override getTitleResource()97 protected int getTitleResource() { 98 return R.string.nls_test; 99 } 100 101 @Override getInstructionsResource()102 protected int getInstructionsResource() { 103 return R.string.nls_info; 104 } 105 106 // Test Setup 107 108 @Override createTestItems()109 protected List<InteractiveTestCase> createTestItems() { 110 List<InteractiveTestCase> tests = new ArrayList<>(17); 111 ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); 112 if (am.isLowRamDevice()) { 113 tests.add(new CannotBeEnabledTest()); 114 tests.add(new ServiceStoppedTest()); 115 tests.add(new NotificationNotReceivedTest()); 116 } else { 117 tests.add(new IsEnabledTest()); 118 tests.add(new ServiceStartedTest()); 119 tests.add(new NotificationReceivedTest()); 120 tests.add(new DataIntactTest()); 121 tests.add(new AudiblyAlertedTest()); 122 tests.add(new DismissOneTest()); 123 tests.add(new DismissOneWithReasonTest()); 124 tests.add(new DismissOneWithStatsTest()); 125 tests.add(new DismissAllTest()); 126 tests.add(new SnoozeNotificationForTimeTest()); 127 tests.add(new SnoozeNotificationForTimeCancelTest()); 128 tests.add(new GetSnoozedNotificationTest()); 129 tests.add(new EnableHintsTest()); 130 tests.add(new ReceiveAppBlockNoticeTest()); 131 tests.add(new ReceiveAppUnblockNoticeTest()); 132 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 133 tests.add(new ReceiveChannelBlockNoticeTest()); 134 tests.add(new ReceiveGroupBlockNoticeTest()); 135 } 136 tests.add(new RequestUnbindTest()); 137 tests.add(new RequestBindTest()); 138 tests.add(new MessageBundleTest()); 139 tests.add(new EnableHintsTest()); 140 tests.add(new IsDisabledTest()); 141 tests.add(new ServiceStoppedTest()); 142 tests.add(new NotificationNotReceivedTest()); 143 } 144 return tests; 145 } 146 createChannels()147 private void createChannels() { 148 NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, 149 NOTIFICATION_CHANNEL_ID, IMPORTANCE_LOW); 150 NotificationChannel noisyChannel = new NotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID, 151 NOISY_NOTIFICATION_CHANNEL_ID, IMPORTANCE_MAX); 152 noisyChannel.setVibrationPattern(new long[]{100, 0, 100}); 153 mNm.createNotificationChannel(channel); 154 mNm.createNotificationChannel(noisyChannel); 155 } 156 deleteChannels()157 private void deleteChannels() { 158 mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID); 159 mNm.deleteNotificationChannel(NOISY_NOTIFICATION_CHANNEL_ID); 160 } 161 162 @SuppressLint("NewApi") sendNotifications()163 private void sendNotifications() { 164 mTag1 = UUID.randomUUID().toString(); 165 Log.d(TAG, "Sending " + mTag1); 166 mTag2 = UUID.randomUUID().toString(); 167 Log.d(TAG, "Sending " + mTag2); 168 mTag3 = UUID.randomUUID().toString(); 169 Log.d(TAG, "Sending " + mTag3); 170 171 mWhen1 = System.currentTimeMillis() + 1; 172 mWhen2 = System.currentTimeMillis() + 2; 173 mWhen3 = System.currentTimeMillis() + 3; 174 175 mIcon1 = R.drawable.ic_stat_alice; 176 mIcon2 = R.drawable.ic_stat_bob; 177 mIcon3 = R.drawable.ic_stat_charlie; 178 179 mId1 = NOTIFICATION_ID + 1; 180 mId2 = NOTIFICATION_ID + 2; 181 mId3 = NOTIFICATION_ID + 3; 182 183 mPackageString = "com.android.cts.verifier"; 184 185 Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 186 .setContentTitle("ClearTest 1") 187 .setContentText(mTag1) 188 .setSmallIcon(mIcon1) 189 .setWhen(mWhen1) 190 .setDeleteIntent(makeIntent(1, mTag1)) 191 .setOnlyAlertOnce(true) 192 .build(); 193 mNm.notify(mTag1, mId1, n1); 194 mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE; 195 196 Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 197 .setContentTitle("ClearTest 2") 198 .setContentText(mTag2) 199 .setSmallIcon(mIcon2) 200 .setWhen(mWhen2) 201 .setDeleteIntent(makeIntent(2, mTag2)) 202 .setAutoCancel(true) 203 .build(); 204 mNm.notify(mTag2, mId2, n2); 205 mFlag2 = Notification.FLAG_AUTO_CANCEL; 206 207 Notification n3 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 208 .setContentTitle("ClearTest 3") 209 .setContentText(mTag3) 210 .setSmallIcon(mIcon3) 211 .setWhen(mWhen3) 212 .setDeleteIntent(makeIntent(3, mTag3)) 213 .setAutoCancel(true) 214 .setOnlyAlertOnce(true) 215 .build(); 216 mNm.notify(mTag3, mId3, n3); 217 mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL; 218 } 219 sendNoisyNotification()220 private void sendNoisyNotification() { 221 mTag4 = UUID.randomUUID().toString(); 222 Log.d(TAG, "Sending " + mTag4); 223 224 mWhen4 = System.currentTimeMillis() + 4; 225 mIcon4 = R.drawable.ic_stat_charlie; 226 mId4 = NOTIFICATION_ID + 4; 227 mPackageString = "com.android.cts.verifier"; 228 229 Notification n1 = new Notification.Builder(mContext, NOISY_NOTIFICATION_CHANNEL_ID) 230 .setContentTitle("NoisyTest 1") 231 .setContentText(mTag4) 232 .setSmallIcon(mIcon4) 233 .setWhen(mWhen4) 234 .setDeleteIntent(makeIntent(4, mTag4)) 235 .setCategory(Notification.CATEGORY_REMINDER) 236 .build(); 237 mNm.notify(mTag4, mId4, n1); 238 } 239 240 // Tests 241 private class NotificationReceivedTest extends InteractiveTestCase { 242 @Override inflate(ViewGroup parent)243 protected View inflate(ViewGroup parent) { 244 return createAutoItem(parent, R.string.nls_note_received); 245 246 } 247 248 @Override setUp()249 protected void setUp() { 250 createChannels(); 251 sendNotifications(); 252 status = READY; 253 } 254 255 @Override tearDown()256 protected void tearDown() { 257 mNm.cancelAll(); 258 MockListener.getInstance().resetData(); 259 deleteChannels(); 260 } 261 262 @Override test()263 protected void test() { 264 List<String> result = new ArrayList<>(MockListener.getInstance().mPosted); 265 if (result.size() > 0 && result.contains(mTag1)) { 266 status = PASS; 267 } else { 268 logFail(); 269 status = FAIL; 270 } 271 } 272 } 273 274 /** 275 * Creates a notification channel. Sends the user to settings to block the channel. Waits 276 * to receive the broadcast that the channel was blocked, and confirms that the broadcast 277 * contains the correct extras. 278 */ 279 protected class ReceiveChannelBlockNoticeTest extends InteractiveTestCase { 280 private String mChannelId; 281 private int mRetries = 2; 282 private View mView; 283 @Override inflate(ViewGroup parent)284 protected View inflate(ViewGroup parent) { 285 mView = createNlsSettingsItem(parent, R.string.nls_block_channel); 286 Button button = mView.findViewById(R.id.nls_action_button); 287 button.setEnabled(false); 288 return mView; 289 } 290 291 @Override setUp()292 protected void setUp() { 293 mChannelId = UUID.randomUUID().toString(); 294 NotificationChannel channel = new NotificationChannel( 295 mChannelId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW); 296 mNm.createNotificationChannel(channel); 297 status = READY; 298 Button button = mView.findViewById(R.id.nls_action_button); 299 button.setEnabled(true); 300 } 301 302 @Override autoStart()303 boolean autoStart() { 304 return true; 305 } 306 307 @Override test()308 protected void test() { 309 NotificationChannel channel = mNm.getNotificationChannel(mChannelId); 310 SharedPreferences prefs = mContext.getSharedPreferences( 311 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 312 313 if (channel.getImportance() == IMPORTANCE_NONE) { 314 if (prefs.contains(mChannelId) && prefs.getBoolean(mChannelId, false)) { 315 status = PASS; 316 } else { 317 if (mRetries > 0) { 318 mRetries--; 319 status = RETEST; 320 } else { 321 status = FAIL; 322 } 323 } 324 } else { 325 // user hasn't jumped to settings to block the channel yet 326 status = WAIT_FOR_USER; 327 } 328 329 next(); 330 } 331 tearDown()332 protected void tearDown() { 333 MockListener.getInstance().resetData(); 334 mNm.deleteNotificationChannel(mChannelId); 335 SharedPreferences prefs = mContext.getSharedPreferences( 336 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 337 SharedPreferences.Editor editor = prefs.edit(); 338 editor.remove(mChannelId); 339 } 340 341 @Override getIntent()342 protected Intent getIntent() { 343 return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) 344 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()) 345 .putExtra(EXTRA_CHANNEL_ID, mChannelId); 346 } 347 } 348 349 /** 350 * Creates a notification channel group. Sends the user to settings to block the group. Waits 351 * to receive the broadcast that the group was blocked, and confirms that the broadcast contains 352 * the correct extras. 353 */ 354 protected class ReceiveGroupBlockNoticeTest extends InteractiveTestCase { 355 private String mGroupId; 356 private int mRetries = 2; 357 private View mView; 358 @Override inflate(ViewGroup parent)359 protected View inflate(ViewGroup parent) { 360 mView = createNlsSettingsItem(parent, R.string.nls_block_group); 361 Button button = mView.findViewById(R.id.nls_action_button); 362 button.setEnabled(false); 363 return mView; 364 } 365 366 @Override setUp()367 protected void setUp() { 368 mGroupId = UUID.randomUUID().toString(); 369 NotificationChannelGroup group 370 = new NotificationChannelGroup(mGroupId, "ReceiveChannelGroupBlockNoticeTest"); 371 mNm.createNotificationChannelGroup(group); 372 NotificationChannel channel = new NotificationChannel( 373 mGroupId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW); 374 channel.setGroup(mGroupId); 375 mNm.createNotificationChannel(channel); 376 status = READY; 377 Button button = mView.findViewById(R.id.nls_action_button); 378 button.setEnabled(true); 379 } 380 381 @Override autoStart()382 boolean autoStart() { 383 return true; 384 } 385 386 @Override test()387 protected void test() { 388 NotificationChannelGroup group = mNm.getNotificationChannelGroup(mGroupId); 389 SharedPreferences prefs = mContext.getSharedPreferences( 390 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 391 392 if (group.isBlocked()) { 393 if (prefs.contains(mGroupId) && prefs.getBoolean(mGroupId, false)) { 394 status = PASS; 395 } else { 396 if (mRetries > 0) { 397 mRetries--; 398 status = RETEST; 399 } else { 400 status = FAIL; 401 } 402 } 403 } else { 404 // user hasn't jumped to settings to block the group yet 405 status = WAIT_FOR_USER; 406 } 407 408 next(); 409 } 410 tearDown()411 protected void tearDown() { 412 MockListener.getInstance().resetData(); 413 mNm.deleteNotificationChannelGroup(mGroupId); 414 SharedPreferences prefs = mContext.getSharedPreferences( 415 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 416 SharedPreferences.Editor editor = prefs.edit(); 417 editor.remove(mGroupId); 418 } 419 420 @Override getIntent()421 protected Intent getIntent() { 422 return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) 423 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 424 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()); 425 } 426 } 427 428 /** 429 * Sends the user to settings to block the app. Waits to receive the broadcast that the app was 430 * blocked, and confirms that the broadcast contains the correct extras. 431 */ 432 protected class ReceiveAppBlockNoticeTest extends InteractiveTestCase { 433 private int mRetries = 2; 434 private View mView; 435 @Override inflate(ViewGroup parent)436 protected View inflate(ViewGroup parent) { 437 mView = createNlsSettingsItem(parent, R.string.nls_block_app); 438 Button button = mView.findViewById(R.id.nls_action_button); 439 button.setEnabled(false); 440 return mView; 441 } 442 443 @Override setUp()444 protected void setUp() { 445 status = READY; 446 Button button = mView.findViewById(R.id.nls_action_button); 447 button.setEnabled(true); 448 } 449 450 @Override autoStart()451 boolean autoStart() { 452 return true; 453 } 454 455 @Override test()456 protected void test() { 457 SharedPreferences prefs = mContext.getSharedPreferences( 458 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 459 460 if (!mNm.areNotificationsEnabled()) { 461 Log.d(TAG, "Got broadcast " + prefs.contains(mContext.getPackageName())); 462 Log.d(TAG, "Broadcast contains correct data? " + 463 prefs.getBoolean(mContext.getPackageName(), false)); 464 if (prefs.contains(mContext.getPackageName()) 465 && prefs.getBoolean(mContext.getPackageName(), false)) { 466 status = PASS; 467 } else { 468 if (mRetries > 0) { 469 mRetries--; 470 status = RETEST; 471 } else { 472 status = FAIL; 473 } 474 } 475 } else { 476 Log.d(TAG, "Notifications still enabled"); 477 // user hasn't jumped to settings to block the app yet 478 status = WAIT_FOR_USER; 479 } 480 481 next(); 482 } 483 tearDown()484 protected void tearDown() { 485 MockListener.getInstance().resetData(); 486 SharedPreferences prefs = mContext.getSharedPreferences( 487 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 488 SharedPreferences.Editor editor = prefs.edit(); 489 editor.remove(mContext.getPackageName()); 490 } 491 492 @Override getIntent()493 protected Intent getIntent() { 494 return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) 495 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 496 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()); 497 } 498 } 499 500 /** 501 * Sends the user to settings to unblock the app. Waits to receive the broadcast that the app 502 * was unblocked, and confirms that the broadcast contains the correct extras. 503 */ 504 protected class ReceiveAppUnblockNoticeTest extends InteractiveTestCase { 505 private int mRetries = 2; 506 private View mView; 507 @Override inflate(ViewGroup parent)508 protected View inflate(ViewGroup parent) { 509 mView = createNlsSettingsItem(parent, R.string.nls_unblock_app); 510 Button button = mView.findViewById(R.id.nls_action_button); 511 button.setEnabled(false); 512 return mView; 513 } 514 515 @Override setUp()516 protected void setUp() { 517 status = READY; 518 Button button = mView.findViewById(R.id.nls_action_button); 519 button.setEnabled(true); 520 } 521 522 @Override autoStart()523 boolean autoStart() { 524 return true; 525 } 526 527 @Override test()528 protected void test() { 529 SharedPreferences prefs = mContext.getSharedPreferences( 530 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 531 532 if (mNm.areNotificationsEnabled()) { 533 if (prefs.contains(mContext.getPackageName()) 534 && !prefs.getBoolean(mContext.getPackageName(), true)) { 535 status = PASS; 536 } else { 537 if (mRetries > 0) { 538 mRetries--; 539 status = RETEST; 540 } else { 541 status = FAIL; 542 } 543 } 544 } else { 545 // user hasn't jumped to settings to block the app yet 546 status = WAIT_FOR_USER; 547 } 548 549 next(); 550 } 551 tearDown()552 protected void tearDown() { 553 MockListener.getInstance().resetData(); 554 SharedPreferences prefs = mContext.getSharedPreferences( 555 NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE); 556 SharedPreferences.Editor editor = prefs.edit(); 557 editor.remove(mContext.getPackageName()); 558 } 559 560 @Override getIntent()561 protected Intent getIntent() { 562 return new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) 563 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 564 .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName()); 565 } 566 } 567 568 private class DataIntactTest extends InteractiveTestCase { 569 @Override inflate(ViewGroup parent)570 protected View inflate(ViewGroup parent) { 571 return createAutoItem(parent, R.string.nls_payload_intact); 572 } 573 574 @Override setUp()575 protected void setUp() { 576 createChannels(); 577 sendNotifications(); 578 status = READY; 579 } 580 581 @Override test()582 protected void test() { 583 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 584 585 Set<String> found = new HashSet<String>(); 586 if (result.size() == 0) { 587 status = FAIL; 588 return; 589 } 590 boolean pass = true; 591 for (JSONObject payload : result) { 592 try { 593 pass &= checkEquals(mPackageString, 594 payload.getString(JSON_PACKAGE), 595 "data integrity test: notification package (%s, %s)"); 596 String tag = payload.getString(JSON_TAG); 597 if (mTag1.equals(tag)) { 598 found.add(mTag1); 599 pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON), 600 "data integrity test: notification icon (%d, %d)"); 601 pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS), 602 "data integrity test: notification flags (%d, %d)"); 603 pass &= checkEquals(mId1, payload.getInt(JSON_ID), 604 "data integrity test: notification ID (%d, %d)"); 605 pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN), 606 "data integrity test: notification when (%d, %d)"); 607 } else if (mTag2.equals(tag)) { 608 found.add(mTag2); 609 pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON), 610 "data integrity test: notification icon (%d, %d)"); 611 pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS), 612 "data integrity test: notification flags (%d, %d)"); 613 pass &= checkEquals(mId2, payload.getInt(JSON_ID), 614 "data integrity test: notification ID (%d, %d)"); 615 pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN), 616 "data integrity test: notification when (%d, %d)"); 617 } else if (mTag3.equals(tag)) { 618 found.add(mTag3); 619 pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON), 620 "data integrity test: notification icon (%d, %d)"); 621 pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS), 622 "data integrity test: notification flags (%d, %d)"); 623 pass &= checkEquals(mId3, payload.getInt(JSON_ID), 624 "data integrity test: notification ID (%d, %d)"); 625 pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN), 626 "data integrity test: notification when (%d, %d)"); 627 } 628 } catch (JSONException e) { 629 pass = false; 630 Log.e(TAG, "failed to unpack data from mocklistener", e); 631 } 632 } 633 634 pass &= found.size() >= 3; 635 status = pass ? PASS : FAIL; 636 } 637 638 @Override tearDown()639 protected void tearDown() { 640 mNm.cancelAll(); 641 MockListener.getInstance().resetData(); 642 deleteChannels(); 643 } 644 } 645 646 private class AudiblyAlertedTest extends InteractiveTestCase { 647 @Override inflate(ViewGroup parent)648 protected View inflate(ViewGroup parent) { 649 return createAutoItem(parent, R.string.nls_audibly_alerted); 650 } 651 652 @Override setUp()653 protected void setUp() { 654 createChannels(); 655 sendNotifications(); 656 sendNoisyNotification(); 657 status = READY; 658 } 659 660 @Override test()661 protected void test() { 662 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 663 664 Set<String> found = new HashSet<>(); 665 if (result.size() == 0) { 666 status = FAIL; 667 return; 668 } 669 boolean pass = true; 670 for (JSONObject payload : result) { 671 try { 672 String tag = payload.getString(JSON_TAG); 673 if (mTag4.equals(tag)) { 674 found.add(mTag4); 675 boolean lastAudiblyAlertedSet 676 = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > -1; 677 if (!lastAudiblyAlertedSet) { 678 logWithStack( 679 "noisy notification test: getLastAudiblyAlertedMillis not set"); 680 } 681 pass &= lastAudiblyAlertedSet; 682 } else if (payload.getString(JSON_PACKAGE).equals(mPackageString)) { 683 found.add(tag); 684 boolean lastAudiblyAlertedSet 685 = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > -1; 686 if (lastAudiblyAlertedSet) { 687 logWithStack( 688 "noisy notification test: getLastAudiblyAlertedMillis set " 689 + "incorrectly"); 690 } 691 pass &= !lastAudiblyAlertedSet; 692 } 693 } catch (JSONException e) { 694 pass = false; 695 Log.e(TAG, "failed to unpack data from mocklistener", e); 696 } 697 } 698 699 pass &= found.size() >= 4; 700 status = pass ? PASS : FAIL; 701 } 702 703 @Override tearDown()704 protected void tearDown() { 705 mNm.cancelAll(); 706 MockListener.getInstance().resetData(); 707 deleteChannels(); 708 } 709 } 710 711 private class DismissOneTest extends InteractiveTestCase { 712 @Override inflate(ViewGroup parent)713 protected View inflate(ViewGroup parent) { 714 return createAutoItem(parent, R.string.nls_clear_one); 715 } 716 717 @Override setUp()718 protected void setUp() { 719 createChannels(); 720 sendNotifications(); 721 status = READY; 722 } 723 724 @Override test()725 protected void test() { 726 if (status == READY) { 727 MockListener.getInstance().cancelNotification( 728 MockListener.getInstance().getKeyForTag(mTag1)); 729 status = RETEST; 730 } else { 731 List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved); 732 if (result.size() != 0 733 && result.contains(mTag1) 734 && !result.contains(mTag2) 735 && !result.contains(mTag3)) { 736 status = PASS; 737 } else { 738 logFail(); 739 status = FAIL; 740 } 741 } 742 } 743 744 @Override tearDown()745 protected void tearDown() { 746 mNm.cancelAll(); 747 deleteChannels(); 748 MockListener.getInstance().resetData(); 749 } 750 } 751 752 private class DismissOneWithReasonTest extends InteractiveTestCase { 753 int mRetries = 3; 754 755 @Override inflate(ViewGroup parent)756 protected View inflate(ViewGroup parent) { 757 return createAutoItem(parent, R.string.nls_clear_one_reason); 758 } 759 760 @Override setUp()761 protected void setUp() { 762 createChannels(); 763 sendNotifications(); 764 status = READY; 765 } 766 767 @Override test()768 protected void test() { 769 if (status == READY) { 770 MockListener.getInstance().cancelNotification( 771 MockListener.getInstance().getKeyForTag(mTag1)); 772 status = RETEST; 773 } else { 774 List<JSONObject> result = 775 new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); 776 boolean pass = false; 777 for (JSONObject payload : result) { 778 try { 779 pass |= (checkEquals(mTag1, 780 payload.getString(JSON_TAG), 781 "data dismissal test: notification tag (%s, %s)") 782 && checkEquals(REASON_LISTENER_CANCEL, 783 payload.getInt(JSON_REASON), 784 "data dismissal test: reason (%d, %d)")); 785 if(pass) { 786 break; 787 } 788 } catch (JSONException e) { 789 e.printStackTrace(); 790 } 791 } 792 if (pass) { 793 status = PASS; 794 } else { 795 if (--mRetries > 0) { 796 sleep(100); 797 status = RETEST; 798 } else { 799 status = FAIL; 800 } 801 } 802 } 803 } 804 805 @Override tearDown()806 protected void tearDown() { 807 mNm.cancelAll(); 808 deleteChannels(); 809 MockListener.getInstance().resetData(); 810 } 811 } 812 813 private class DismissOneWithStatsTest extends InteractiveTestCase { 814 int mRetries = 3; 815 816 @Override inflate(ViewGroup parent)817 protected View inflate(ViewGroup parent) { 818 return createAutoItem(parent, R.string.nls_clear_one_stats); 819 } 820 821 @Override setUp()822 protected void setUp() { 823 createChannels(); 824 sendNotifications(); 825 status = READY; 826 } 827 828 @Override test()829 protected void test() { 830 if (status == READY) { 831 MockListener.getInstance().cancelNotification( 832 MockListener.getInstance().getKeyForTag(mTag1)); 833 status = RETEST; 834 } else { 835 List<JSONObject> result = 836 new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); 837 boolean pass = true; 838 for (JSONObject payload : result) { 839 try { 840 pass &= (payload.getBoolean(JSON_STATS) == false); 841 } catch (JSONException e) { 842 e.printStackTrace(); 843 pass = false; 844 } 845 } 846 if (pass) { 847 status = PASS; 848 } else { 849 if (--mRetries > 0) { 850 sleep(100); 851 status = RETEST; 852 } else { 853 logFail("Notification listener got populated stats object."); 854 status = FAIL; 855 } 856 } 857 } 858 } 859 860 @Override tearDown()861 protected void tearDown() { 862 mNm.cancelAll(); 863 deleteChannels(); 864 MockListener.getInstance().resetData(); 865 } 866 } 867 868 private class DismissAllTest extends InteractiveTestCase { 869 @Override inflate(ViewGroup parent)870 protected View inflate(ViewGroup parent) { 871 return createAutoItem(parent, R.string.nls_clear_all); 872 } 873 874 @Override setUp()875 protected void setUp() { 876 createChannels(); 877 sendNotifications(); 878 status = READY; 879 } 880 881 @Override test()882 protected void test() { 883 if (status == READY) { 884 MockListener.getInstance().cancelAllNotifications(); 885 status = RETEST; 886 } else { 887 List<String> result = new ArrayList<>(MockListener.getInstance().mRemoved); 888 if (result.size() != 0 889 && result.contains(mTag1) 890 && result.contains(mTag2) 891 && result.contains(mTag3)) { 892 status = PASS; 893 } else { 894 logFail(); 895 status = FAIL; 896 } 897 } 898 } 899 900 @Override tearDown()901 protected void tearDown() { 902 mNm.cancelAll(); 903 deleteChannels(); 904 MockListener.getInstance().resetData(); 905 } 906 } 907 908 private class IsDisabledTest extends InteractiveTestCase { 909 @Override inflate(ViewGroup parent)910 protected View inflate(ViewGroup parent) { 911 return createNlsSettingsItem(parent, R.string.nls_disable_service); 912 } 913 914 @Override autoStart()915 boolean autoStart() { 916 return true; 917 } 918 919 @Override test()920 protected void test() { 921 String listeners = Secure.getString(getContentResolver(), 922 ENABLED_NOTIFICATION_LISTENERS); 923 if (listeners == null || !listeners.contains(LISTENER_PATH)) { 924 status = PASS; 925 } else { 926 status = WAIT_FOR_USER; 927 } 928 } 929 930 @Override tearDown()931 protected void tearDown() { 932 MockListener.getInstance().resetData(); 933 } 934 935 @Override getIntent()936 protected Intent getIntent() { 937 return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); 938 } 939 } 940 941 private class ServiceStoppedTest extends InteractiveTestCase { 942 int mRetries = 3; 943 @Override inflate(ViewGroup parent)944 protected View inflate(ViewGroup parent) { 945 return createAutoItem(parent, R.string.nls_service_stopped); 946 } 947 948 @Override test()949 protected void test() { 950 if (mNm.getEffectsSuppressor() == null && (MockListener.getInstance() == null 951 || !MockListener.getInstance().isConnected)) { 952 status = PASS; 953 } else { 954 if (--mRetries > 0) { 955 sleep(100); 956 status = RETEST; 957 } else { 958 status = FAIL; 959 } 960 } 961 } 962 963 @Override getIntent()964 protected Intent getIntent() { 965 return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); 966 } 967 } 968 969 private class NotificationNotReceivedTest extends InteractiveTestCase { 970 @Override inflate(ViewGroup parent)971 protected View inflate(ViewGroup parent) { 972 return createAutoItem(parent, R.string.nls_note_missed); 973 974 } 975 976 @Override setUp()977 protected void setUp() { 978 createChannels(); 979 sendNotifications(); 980 status = READY; 981 } 982 983 @Override test()984 protected void test() { 985 if (MockListener.getInstance() == null) { 986 status = PASS; 987 } else { 988 List<String> result = new ArrayList<>(MockListener.getInstance().mPosted); 989 if (result.size() == 0) { 990 status = PASS; 991 } else { 992 logFail(); 993 status = FAIL; 994 } 995 } 996 next(); 997 } 998 999 @Override tearDown()1000 protected void tearDown() { 1001 mNm.cancelAll(); 1002 deleteChannels(); 1003 if (MockListener.getInstance() != null) { 1004 MockListener.getInstance().resetData(); 1005 } 1006 } 1007 } 1008 1009 private class RequestUnbindTest extends InteractiveTestCase { 1010 int mRetries = 5; 1011 1012 @Override inflate(ViewGroup parent)1013 protected View inflate(ViewGroup parent) { 1014 return createAutoItem(parent, R.string.nls_snooze); 1015 1016 } 1017 1018 @Override setUp()1019 protected void setUp() { 1020 status = READY; 1021 MockListener.getInstance().requestListenerHints( 1022 MockListener.HINT_HOST_DISABLE_CALL_EFFECTS); 1023 } 1024 1025 @Override test()1026 protected void test() { 1027 if (status == READY) { 1028 MockListener.getInstance().requestUnbind(); 1029 status = RETEST; 1030 } else { 1031 if (mNm.getEffectsSuppressor() == null && !MockListener.getInstance().isConnected) { 1032 status = PASS; 1033 } else { 1034 if (--mRetries > 0) { 1035 status = RETEST; 1036 } else { 1037 logFail(); 1038 status = FAIL; 1039 } 1040 } 1041 next(); 1042 } 1043 } 1044 } 1045 1046 private class RequestBindTest extends InteractiveTestCase { 1047 int mRetries = 5; 1048 1049 @Override inflate(ViewGroup parent)1050 protected View inflate(ViewGroup parent) { 1051 return createAutoItem(parent, R.string.nls_unsnooze); 1052 1053 } 1054 1055 @Override test()1056 protected void test() { 1057 if (status == READY) { 1058 MockListener.requestRebind(MockListener.COMPONENT_NAME); 1059 status = RETEST; 1060 } else { 1061 if (MockListener.getInstance().isConnected) { 1062 status = PASS; 1063 next(); 1064 } else { 1065 if (--mRetries > 0) { 1066 status = RETEST; 1067 next(); 1068 } else { 1069 logFail(); 1070 status = FAIL; 1071 } 1072 } 1073 } 1074 } 1075 } 1076 1077 private class EnableHintsTest extends InteractiveTestCase { 1078 @Override inflate(ViewGroup parent)1079 protected View inflate(ViewGroup parent) { 1080 return createAutoItem(parent, R.string.nls_hints); 1081 1082 } 1083 1084 @Override test()1085 protected void test() { 1086 if (status == READY) { 1087 MockListener.getInstance().requestListenerHints( 1088 MockListener.HINT_HOST_DISABLE_CALL_EFFECTS); 1089 status = RETEST; 1090 } else { 1091 int result = MockListener.getInstance().getCurrentListenerHints(); 1092 if ((result & MockListener.HINT_HOST_DISABLE_CALL_EFFECTS) 1093 == MockListener.HINT_HOST_DISABLE_CALL_EFFECTS) { 1094 status = PASS; 1095 next(); 1096 } else { 1097 logFail(); 1098 status = FAIL; 1099 } 1100 } 1101 } 1102 } 1103 1104 private class SnoozeNotificationForTimeTest extends InteractiveTestCase { 1105 final static int READY_TO_SNOOZE = 0; 1106 final static int SNOOZED = 1; 1107 final static int READY_TO_CHECK_FOR_UNSNOOZE = 2; 1108 int state = -1; 1109 long snoozeTime = 3000; 1110 1111 @Override inflate(ViewGroup parent)1112 protected View inflate(ViewGroup parent) { 1113 return createAutoItem(parent, R.string.nls_snooze_one_time); 1114 } 1115 1116 @Override setUp()1117 protected void setUp() { 1118 createChannels(); 1119 sendNotifications(); 1120 status = READY; 1121 state = READY_TO_SNOOZE; 1122 delay(); 1123 } 1124 1125 @Override test()1126 protected void test() { 1127 status = RETEST; 1128 if (state == READY_TO_SNOOZE) { 1129 MockListener.getInstance().snoozeNotification( 1130 MockListener.getInstance().getKeyForTag(mTag1), snoozeTime); 1131 state = SNOOZED; 1132 } else if (state == SNOOZED) { 1133 List<JSONObject> result = 1134 new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); 1135 boolean pass = false; 1136 for (JSONObject payload : result) { 1137 try { 1138 pass |= (checkEquals(mTag1, 1139 payload.getString(JSON_TAG), 1140 "data dismissal test: notification tag (%s, %s)") 1141 && checkEquals(MockListener.REASON_SNOOZED, 1142 payload.getInt(JSON_REASON), 1143 "data dismissal test: reason (%d, %d)")); 1144 if (pass) { 1145 break; 1146 } 1147 } catch (JSONException e) { 1148 e.printStackTrace(); 1149 } 1150 } 1151 if (!pass) { 1152 logFail(); 1153 status = FAIL; 1154 next(); 1155 return; 1156 } else { 1157 state = READY_TO_CHECK_FOR_UNSNOOZE; 1158 } 1159 } else { 1160 List<String> result = new ArrayList<>(MockListener.getInstance().mPosted); 1161 if (result.size() > 0 && result.contains(mTag1)) { 1162 status = PASS; 1163 } else { 1164 logFail(); 1165 status = FAIL; 1166 } 1167 } 1168 } 1169 1170 @Override tearDown()1171 protected void tearDown() { 1172 mNm.cancelAll(); 1173 deleteChannels(); 1174 MockListener.getInstance().resetData(); 1175 delay(); 1176 } 1177 } 1178 1179 /** 1180 * Posts notifications, snoozes one of them. Verifies that it is snoozed. Cancels all 1181 * notifications and reposts them. Confirms that the original notification is still snoozed. 1182 */ 1183 private class SnoozeNotificationForTimeCancelTest extends InteractiveTestCase { 1184 1185 final static int READY_TO_SNOOZE = 0; 1186 final static int SNOOZED = 1; 1187 final static int READY_TO_CHECK_FOR_SNOOZE = 2; 1188 int state = -1; 1189 long snoozeTime = 10000; 1190 private String tag; 1191 1192 @Override inflate(ViewGroup parent)1193 protected View inflate(ViewGroup parent) { 1194 return createAutoItem(parent, R.string.nls_snooze_one_time); 1195 } 1196 1197 @Override setUp()1198 protected void setUp() { 1199 createChannels(); 1200 sendNotifications(); 1201 tag = mTag1; 1202 status = READY; 1203 state = READY_TO_SNOOZE; 1204 delay(); 1205 } 1206 1207 @Override test()1208 protected void test() { 1209 status = RETEST; 1210 if (state == READY_TO_SNOOZE) { 1211 MockListener.getInstance().snoozeNotification( 1212 MockListener.getInstance().getKeyForTag(tag), snoozeTime); 1213 state = SNOOZED; 1214 } else if (state == SNOOZED) { 1215 List<String> result = getSnoozed(); 1216 if (result.size() >= 1 1217 && result.contains(tag)) { 1218 // cancel and repost 1219 sendNotifications(); 1220 state = READY_TO_CHECK_FOR_SNOOZE; 1221 } else { 1222 logFail(); 1223 status = FAIL; 1224 } 1225 } else { 1226 List<String> result = getSnoozed(); 1227 if (result.size() >= 1 1228 && result.contains(tag)) { 1229 status = PASS; 1230 } else { 1231 logFail(); 1232 status = FAIL; 1233 } 1234 } 1235 } 1236 getSnoozed()1237 private List<String> getSnoozed() { 1238 List<String> result = new ArrayList<>(); 1239 StatusBarNotification[] snoozed = MockListener.getInstance().getSnoozedNotifications(); 1240 for (StatusBarNotification sbn : snoozed) { 1241 result.add(sbn.getTag()); 1242 } 1243 return result; 1244 } 1245 1246 @Override tearDown()1247 protected void tearDown() { 1248 mNm.cancelAll(); 1249 deleteChannels(); 1250 MockListener.getInstance().resetData(); 1251 } 1252 } 1253 1254 private class GetSnoozedNotificationTest extends InteractiveTestCase { 1255 final static int READY_TO_SNOOZE = 0; 1256 final static int SNOOZED = 1; 1257 final static int READY_TO_CHECK_FOR_GET_SNOOZE = 2; 1258 int state = -1; 1259 long snoozeTime = 30000; 1260 1261 @Override inflate(ViewGroup parent)1262 protected View inflate(ViewGroup parent) { 1263 return createAutoItem(parent, R.string.nls_get_snoozed); 1264 } 1265 1266 @Override setUp()1267 protected void setUp() { 1268 createChannels(); 1269 sendNotifications(); 1270 status = READY; 1271 state = READY_TO_SNOOZE; 1272 } 1273 1274 @Override test()1275 protected void test() { 1276 status = RETEST; 1277 if (state == READY_TO_SNOOZE) { 1278 MockListener.getInstance().snoozeNotification( 1279 MockListener.getInstance().getKeyForTag(mTag1), snoozeTime); 1280 MockListener.getInstance().snoozeNotification( 1281 MockListener.getInstance().getKeyForTag(mTag2), snoozeTime); 1282 state = SNOOZED; 1283 } else if (state == SNOOZED) { 1284 List<JSONObject> result = 1285 new ArrayList<>(MockListener.getInstance().mRemovedReason.values()); 1286 if (result.size() == 0) { 1287 status = FAIL; 1288 return; 1289 } 1290 boolean pass = false; 1291 for (JSONObject payload : result) { 1292 try { 1293 pass |= (checkEquals(mTag1, 1294 payload.getString(JSON_TAG), 1295 "data dismissal test: notification tag (%s, %s)") 1296 && checkEquals(MockListener.REASON_SNOOZED, 1297 payload.getInt(JSON_REASON), 1298 "data dismissal test: reason (%d, %d)")); 1299 if (pass) { 1300 break; 1301 } 1302 } catch (JSONException e) { 1303 e.printStackTrace(); 1304 } 1305 } 1306 if (!pass) { 1307 logFail(); 1308 status = FAIL; 1309 } else { 1310 state = READY_TO_CHECK_FOR_GET_SNOOZE; 1311 } 1312 } else { 1313 List<String> result = new ArrayList<>(); 1314 StatusBarNotification[] snoozed = 1315 MockListener.getInstance().getSnoozedNotifications(); 1316 for (StatusBarNotification sbn : snoozed) { 1317 result.add(sbn.getTag()); 1318 } 1319 if (result.size() >= 2 1320 && result.contains(mTag1) 1321 && result.contains(mTag2)) { 1322 status = PASS; 1323 } else { 1324 logFail(); 1325 status = FAIL; 1326 } 1327 } 1328 } 1329 1330 @Override tearDown()1331 protected void tearDown() { 1332 mNm.cancelAll(); 1333 deleteChannels(); 1334 MockListener.getInstance().resetData(); 1335 delay(); 1336 } 1337 } 1338 1339 /** Tests that the extras {@link Bundle} in a MessagingStyle#Message is preserved. */ 1340 private class MessageBundleTest extends InteractiveTestCase { 1341 private final String extrasKey1 = "extras_key_1"; 1342 private final CharSequence extrasValue1 = "extras_value_1"; 1343 private final String extrasKey2 = "extras_key_2"; 1344 private final CharSequence extrasValue2 = "extras_value_2"; 1345 1346 @Override inflate(ViewGroup parent)1347 protected View inflate(ViewGroup parent) { 1348 return createAutoItem(parent, R.string.msg_extras_preserved); 1349 } 1350 1351 @Override setUp()1352 protected void setUp() { 1353 createChannels(); 1354 sendMessagingNotification(); 1355 status = READY; 1356 } 1357 1358 @Override tearDown()1359 protected void tearDown() { 1360 mNm.cancelAll(); 1361 deleteChannels(); 1362 delay(); 1363 } 1364 sendMessagingNotification()1365 private void sendMessagingNotification() { 1366 mTag1 = UUID.randomUUID().toString(); 1367 mNm.cancelAll(); 1368 mWhen1 = System.currentTimeMillis() + 1; 1369 mIcon1 = R.drawable.ic_stat_alice; 1370 mId1 = NOTIFICATION_ID + 1; 1371 1372 Notification.MessagingStyle.Message msg1 = 1373 new Notification.MessagingStyle.Message("text1", 0 /* timestamp */, "sender1"); 1374 msg1.getExtras().putCharSequence(extrasKey1, extrasValue1); 1375 1376 Notification.MessagingStyle.Message msg2 = 1377 new Notification.MessagingStyle.Message("text2", 1 /* timestamp */, "sender2"); 1378 msg2.getExtras().putCharSequence(extrasKey2, extrasValue2); 1379 1380 Notification.MessagingStyle style = new Notification.MessagingStyle("display_name"); 1381 style.addMessage(msg1); 1382 style.addMessage(msg2); 1383 1384 Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 1385 .setContentTitle("ClearTest 1") 1386 .setContentText(mTag1.toString()) 1387 .setPriority(Notification.PRIORITY_LOW) 1388 .setSmallIcon(mIcon1) 1389 .setWhen(mWhen1) 1390 .setDeleteIntent(makeIntent(1, mTag1)) 1391 .setOnlyAlertOnce(true) 1392 .setStyle(style) 1393 .build(); 1394 mNm.notify(mTag1, mId1, n1); 1395 mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE; 1396 } 1397 1398 // Returns true on success. verifyMessage( NotificationCompat.MessagingStyle.Message message, String extrasKey, CharSequence extrasValue)1399 private boolean verifyMessage( 1400 NotificationCompat.MessagingStyle.Message message, 1401 String extrasKey, 1402 CharSequence extrasValue) { 1403 return message.getExtras() != null 1404 && message.getExtras().getCharSequence(extrasKey) != null 1405 && message.getExtras().getCharSequence(extrasKey).equals(extrasValue); 1406 } 1407 1408 @Override test()1409 protected void test() { 1410 List<Notification> result = 1411 new ArrayList<>(MockListener.getInstance().mPostedNotifications); 1412 if (result.size() != 1 || result.get(0) == null) { 1413 logFail(); 1414 status = FAIL; 1415 next(); 1416 return; 1417 } 1418 // Can only read in MessagingStyle using the compat class. 1419 NotificationCompat.MessagingStyle readStyle = 1420 NotificationCompat.MessagingStyle 1421 .extractMessagingStyleFromNotification( 1422 result.get(0)); 1423 if (readStyle == null || readStyle.getMessages().size() != 2) { 1424 status = FAIL; 1425 logFail(); 1426 next(); 1427 return; 1428 } 1429 1430 if (!verifyMessage(readStyle.getMessages().get(0), extrasKey1, 1431 extrasValue1) 1432 || !verifyMessage( 1433 readStyle.getMessages().get(1), extrasKey2, extrasValue2)) { 1434 status = FAIL; 1435 logFail(); 1436 next(); 1437 return; 1438 } 1439 1440 status = PASS; 1441 } 1442 } 1443 } 1444