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