1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.verifier.notifications; 18 19 import static com.android.cts.verifier.notifications.MockListener.JSON_AMBIENT; 20 import static com.android.cts.verifier.notifications.MockListener.JSON_MATCHES_ZEN_FILTER; 21 import static com.android.cts.verifier.notifications.MockListener.JSON_TAG; 22 23 import android.app.Notification; 24 import android.app.NotificationChannel; 25 import android.app.NotificationManager; 26 import android.content.ContentProviderOperation; 27 import android.content.OperationApplicationException; 28 import android.database.Cursor; 29 import android.media.AudioAttributes; 30 import android.net.Uri; 31 import android.os.Build; 32 import android.os.RemoteException; 33 import android.provider.ContactsContract; 34 import android.provider.ContactsContract.CommonDataKinds.Email; 35 import android.provider.ContactsContract.CommonDataKinds.Phone; 36 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 37 import android.util.Log; 38 import android.view.View; 39 import android.view.ViewGroup; 40 41 import com.android.cts.verifier.R; 42 43 import org.json.JSONException; 44 import org.json.JSONObject; 45 46 import java.util.ArrayList; 47 import java.util.HashSet; 48 import java.util.List; 49 import java.util.Set; 50 51 public class AttentionManagementVerifierActivity 52 extends InteractiveVerifierActivity { 53 private static final String TAG = "AttentionVerifier"; 54 55 private static final String NOTIFICATION_CHANNEL_ID = TAG; 56 private static final String NOTIFICATION_CHANNEL_ID_NOISY = TAG + "/noisy"; 57 private static final String NOTIFICATION_CHANNEL_ID_MEDIA = TAG + "/media"; 58 private static final String NOTIFICATION_CHANNEL_ID_GAME = TAG + "/game"; 59 private static final String ALICE = "Alice"; 60 private static final String ALICE_PHONE = "+16175551212"; 61 private static final String ALICE_EMAIL = "alice@_foo._bar"; 62 private static final String BOB = "Bob"; 63 private static final String BOB_PHONE = "+16505551212";; 64 private static final String BOB_EMAIL = "bob@_foo._bar"; 65 private static final String CHARLIE = "Charlie"; 66 private static final String CHARLIE_PHONE = "+13305551212"; 67 private static final String CHARLIE_EMAIL = "charlie@_foo._bar"; 68 private static final int MODE_NONE = 0; 69 private static final int MODE_URI = 1; 70 private static final int MODE_PHONE = 2; 71 private static final int MODE_EMAIL = 3; 72 private static final int SEND_A = 0x1; 73 private static final int SEND_B = 0x2; 74 private static final int SEND_C = 0x4; 75 private static final int SEND_ALL = SEND_A | SEND_B | SEND_C; 76 77 private Uri mAliceUri; 78 private Uri mBobUri; 79 private Uri mCharlieUri; 80 81 @Override getTitleResource()82 protected int getTitleResource() { 83 return R.string.attention_test; 84 } 85 86 @Override getInstructionsResource()87 protected int getInstructionsResource() { 88 return R.string.attention_info; 89 } 90 91 // Test Setup 92 93 @Override createTestItems()94 protected List<InteractiveTestCase> createTestItems() { 95 List<InteractiveTestCase> tests = new ArrayList<>(17); 96 tests.add(new IsEnabledTest()); 97 tests.add(new ServiceStartedTest()); 98 tests.add(new InsertContactsTest()); 99 tests.add(new NoneInterceptsAllMessagesTest()); 100 tests.add(new NoneInterceptsAlarmEventReminderCategoriesTest()); 101 tests.add(new PriorityInterceptsSomeMessagesTest()); 102 103 if (getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) { 104 // Tests targeting P and above: 105 tests.add(new PriorityInterceptsAlarmsTest()); 106 tests.add(new PriorityInterceptsMediaSystemOtherTest()); 107 } 108 109 tests.add(new AllInterceptsNothingMessagesTest()); 110 tests.add(new AllInterceptsNothingDiffCategoriesTest()); 111 tests.add(new DefaultOrderTest()); 112 tests.add(new PriorityOrderTest()); 113 tests.add(new InterruptionOrderTest()); 114 tests.add(new AmbientBitsTest()); 115 tests.add(new LookupUriOrderTest()); 116 tests.add(new EmailOrderTest()); 117 tests.add(new PhoneOrderTest()); 118 tests.add(new DeleteContactsTest()); 119 return tests; 120 } 121 createChannels()122 private void createChannels() { 123 NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, 124 NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_MIN); 125 mNm.createNotificationChannel(channel); 126 NotificationChannel noisyChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_NOISY, 127 NOTIFICATION_CHANNEL_ID_NOISY, NotificationManager.IMPORTANCE_HIGH); 128 noisyChannel.enableVibration(true); 129 mNm.createNotificationChannel(noisyChannel); 130 NotificationChannel mediaChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_MEDIA, 131 NOTIFICATION_CHANNEL_ID_MEDIA, NotificationManager.IMPORTANCE_HIGH); 132 AudioAttributes.Builder aa = new AudioAttributes.Builder() 133 .setUsage(AudioAttributes.USAGE_MEDIA); 134 mediaChannel.setSound(null, aa.build()); 135 mNm.createNotificationChannel(mediaChannel); 136 NotificationChannel gameChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_GAME, 137 NOTIFICATION_CHANNEL_ID_GAME, NotificationManager.IMPORTANCE_HIGH); 138 AudioAttributes.Builder aa2 = new AudioAttributes.Builder() 139 .setUsage(AudioAttributes.USAGE_GAME); 140 gameChannel.setSound(null, aa2.build()); 141 mNm.createNotificationChannel(gameChannel); 142 } 143 deleteChannels()144 private void deleteChannels() { 145 mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID); 146 mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_NOISY); 147 mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_MEDIA); 148 mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_GAME); 149 } 150 151 // Tests 152 153 private class ServiceStoppedTest extends InteractiveTestCase { 154 int mRetries = 3; 155 @Override inflate(ViewGroup parent)156 protected View inflate(ViewGroup parent) { 157 return createAutoItem(parent, R.string.nls_service_stopped); 158 } 159 160 @Override test()161 protected void test() { 162 if (MockListener.getInstance() == null 163 || !MockListener.getInstance().isConnected) { 164 status = PASS; 165 } else { 166 if (--mRetries > 0) { 167 sleep(100); 168 status = RETEST; 169 } else { 170 status = FAIL; 171 } 172 } 173 } 174 } 175 176 protected class InsertContactsTest extends InteractiveTestCase { 177 @Override inflate(ViewGroup parent)178 protected View inflate(ViewGroup parent) { 179 return createAutoItem(parent, R.string.attention_create_contacts); 180 } 181 182 @Override setUp()183 protected void setUp() { 184 insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, true); 185 insertSingleContact(BOB, BOB_PHONE, BOB_EMAIL, false); 186 // charlie is not in contacts 187 status = READY; 188 } 189 190 @Override test()191 protected void test() { 192 mAliceUri = lookupContact(ALICE_PHONE); 193 mBobUri = lookupContact(BOB_PHONE); 194 mCharlieUri = lookupContact(CHARLIE_PHONE); 195 196 status = PASS; 197 if (mAliceUri == null) { status = FAIL; } 198 if (mBobUri == null) { status = FAIL; } 199 if (mCharlieUri != null) { status = FAIL; } 200 201 if (status == PASS && !isStarred(mAliceUri)) { 202 status = RETEST; 203 Log.i("InsertContactsTest", "Alice is not yet starred"); 204 } else { 205 Log.i("InsertContactsTest", "Alice is: " + mAliceUri); 206 Log.i("InsertContactsTest", "Bob is: " + mBobUri); 207 Log.i("InsertContactsTest", "Charlie is: " + mCharlieUri); 208 next(); 209 } 210 } 211 } 212 213 protected class DeleteContactsTest extends InteractiveTestCase { 214 @Override inflate(ViewGroup parent)215 protected View inflate(ViewGroup parent) { 216 return createAutoItem(parent, R.string.attention_delete_contacts); 217 } 218 219 @Override test()220 protected void test() { 221 final ArrayList<ContentProviderOperation> operationList = new ArrayList<>(); 222 operationList.add(ContentProviderOperation.newDelete(mAliceUri).build()); 223 operationList.add(ContentProviderOperation.newDelete(mBobUri).build()); 224 try { 225 mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList); 226 status = READY; 227 } catch (RemoteException e) { 228 Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); 229 status = FAIL; 230 } catch (OperationApplicationException e) { 231 Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); 232 status = FAIL; 233 } 234 status = PASS; 235 next(); 236 } 237 } 238 239 protected class NoneInterceptsAllMessagesTest extends InteractiveTestCase { 240 @Override inflate(ViewGroup parent)241 protected View inflate(ViewGroup parent) { 242 return createAutoItem(parent, R.string.attention_all_are_filtered); 243 } 244 245 @Override setUp()246 protected void setUp() { 247 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE); 248 createChannels(); 249 sendNotifications(MODE_URI, false, false); 250 status = READY; 251 } 252 253 @Override test()254 protected void test() { 255 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 256 257 Set<String> found = new HashSet<String>(); 258 if (result.size() == 0) { 259 status = FAIL; 260 return; 261 } 262 boolean pass = true; 263 for (JSONObject payload : result) { 264 try { 265 String tag = payload.getString(JSON_TAG); 266 boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER); 267 Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted"); 268 if (found.contains(tag)) { 269 // multiple entries for same notification! 270 pass = false; 271 } else if (ALICE.equals(tag)) { 272 found.add(ALICE); 273 pass &= !zen; 274 } else if (BOB.equals(tag)) { 275 found.add(BOB); 276 pass &= !zen; 277 } else if (CHARLIE.equals(tag)) { 278 found.add(CHARLIE); 279 pass &= !zen; 280 } 281 } catch (JSONException e) { 282 pass = false; 283 Log.e(TAG, "failed to unpack data from mocklistener", e); 284 } 285 } 286 pass &= found.size() == 3; 287 status = pass ? PASS : FAIL; 288 } 289 290 @Override tearDown()291 protected void tearDown() { 292 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL); 293 mNm.cancelAll(); 294 deleteChannels(); 295 MockListener.getInstance().resetData(); 296 } 297 } 298 299 protected class NoneInterceptsAlarmEventReminderCategoriesTest extends InteractiveTestCase { 300 @Override inflate(ViewGroup parent)301 protected View inflate(ViewGroup parent) { 302 return createAutoItem(parent, R.string.attention_all_are_filtered); 303 } 304 305 @Override setUp()306 protected void setUp() { 307 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE); 308 createChannels(); 309 sendEventAlarmReminderNotifications(SEND_ALL); 310 status = READY; 311 } 312 313 @Override test()314 protected void test() { 315 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 316 317 Set<String> found = new HashSet<String>(); 318 if (result.size() == 0) { 319 status = FAIL; 320 return; 321 } 322 boolean pass = true; 323 for (JSONObject payload : result) { 324 try { 325 String tag = payload.getString(JSON_TAG); 326 boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER); 327 Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted"); 328 if (found.contains(tag)) { 329 // multiple entries for same notification! 330 pass = false; 331 } else if (ALICE.equals(tag)) { 332 found.add(ALICE); 333 pass &= !zen; 334 } else if (BOB.equals(tag)) { 335 found.add(BOB); 336 pass &= !zen; 337 } else if (CHARLIE.equals(tag)) { 338 found.add(CHARLIE); 339 pass &= !zen; 340 } 341 } catch (JSONException e) { 342 pass = false; 343 Log.e(TAG, "failed to unpack data from mocklistener", e); 344 } 345 } 346 pass &= found.size() == 3; 347 status = pass ? PASS : FAIL; 348 } 349 350 @Override tearDown()351 protected void tearDown() { 352 mNm.cancelAll(); 353 deleteChannels(); 354 MockListener.getInstance().resetData(); 355 } 356 } 357 358 protected class AllInterceptsNothingMessagesTest extends InteractiveTestCase { 359 @Override inflate(ViewGroup parent)360 protected View inflate(ViewGroup parent) { 361 return createAutoItem(parent, R.string.attention_none_are_filtered_messages); 362 } 363 364 @Override setUp()365 protected void setUp() { 366 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL); 367 createChannels(); 368 sendNotifications(MODE_URI, false, false); // different messages 369 status = READY; 370 } 371 372 @Override test()373 protected void test() { 374 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 375 376 Set<String> found = new HashSet<String>(); 377 if (result.size() == 0) { 378 status = FAIL; 379 return; 380 } 381 boolean pass = true; 382 for (JSONObject payload : result) { 383 try { 384 String tag = payload.getString(JSON_TAG); 385 boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER); 386 Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted"); 387 if (found.contains(tag)) { 388 // multiple entries for same notification! 389 pass = false; 390 } else if (ALICE.equals(tag)) { 391 found.add(ALICE); 392 pass &= zen; 393 } else if (BOB.equals(tag)) { 394 found.add(BOB); 395 pass &= zen; 396 } else if (CHARLIE.equals(tag)) { 397 found.add(CHARLIE); 398 pass &= zen; 399 } 400 } catch (JSONException e) { 401 pass = false; 402 Log.e(TAG, "failed to unpack data from mocklistener", e); 403 } 404 } 405 pass &= found.size() == 3; 406 status = pass ? PASS : FAIL; 407 } 408 409 @Override tearDown()410 protected void tearDown() { 411 mNm.cancelAll(); 412 deleteChannels(); 413 MockListener.getInstance().resetData(); 414 } 415 } 416 417 protected class AllInterceptsNothingDiffCategoriesTest extends InteractiveTestCase { 418 @Override inflate(ViewGroup parent)419 protected View inflate(ViewGroup parent) { 420 return createAutoItem(parent, R.string.attention_none_are_filtered_diff_categories); 421 } 422 423 @Override setUp()424 protected void setUp() { 425 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL); 426 createChannels(); 427 sendEventAlarmReminderNotifications(SEND_ALL); 428 status = READY; 429 } 430 431 @Override test()432 protected void test() { 433 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 434 435 Set<String> found = new HashSet<String>(); 436 if (result.size() == 0) { 437 status = FAIL; 438 return; 439 } 440 boolean pass = true; 441 for (JSONObject payload : result) { 442 try { 443 String tag = payload.getString(JSON_TAG); 444 boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER); 445 Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted"); 446 if (found.contains(tag)) { 447 // multiple entries for same notification! 448 pass = false; 449 } else if (ALICE.equals(tag)) { 450 found.add(ALICE); 451 pass &= zen; 452 } else if (BOB.equals(tag)) { 453 found.add(BOB); 454 pass &= zen; 455 } else if (CHARLIE.equals(tag)) { 456 found.add(CHARLIE); 457 pass &= zen; 458 } 459 } catch (JSONException e) { 460 pass = false; 461 Log.e(TAG, "failed to unpack data from mocklistener", e); 462 } 463 } 464 pass &= found.size() == 3; 465 status = pass ? PASS : FAIL; 466 } 467 468 @Override tearDown()469 protected void tearDown() { 470 mNm.cancelAll(); 471 deleteChannels(); 472 MockListener.getInstance().resetData(); 473 } 474 } 475 476 protected class PriorityInterceptsSomeMessagesTest extends InteractiveTestCase { 477 @Override inflate(ViewGroup parent)478 protected View inflate(ViewGroup parent) { 479 return createAutoItem(parent, R.string.attention_some_are_filtered_messages); 480 } 481 482 @Override setUp()483 protected void setUp() { 484 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY); 485 NotificationManager.Policy policy = mNm.getNotificationPolicy(); 486 policy = new NotificationManager.Policy( 487 NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES, 488 policy.priorityCallSenders, 489 NotificationManager.Policy.PRIORITY_SENDERS_STARRED); 490 mNm.setNotificationPolicy(policy); 491 createChannels(); 492 sendNotifications(MODE_URI, false, false); 493 status = READY; 494 } 495 496 @Override test()497 protected void test() { 498 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 499 500 Set<String> found = new HashSet<String>(); 501 if (result.size() == 0) { 502 status = FAIL; 503 return; 504 } 505 boolean pass = true; 506 for (JSONObject payload : result) { 507 try { 508 String tag = payload.getString(JSON_TAG); 509 boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER); 510 Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted"); 511 if (found.contains(tag)) { 512 // multiple entries for same notification! 513 pass = false; 514 } else if (ALICE.equals(tag)) { 515 found.add(ALICE); 516 pass &= zen; 517 } else if (BOB.equals(tag)) { 518 found.add(BOB); 519 pass &= !zen; 520 } else if (CHARLIE.equals(tag)) { 521 found.add(CHARLIE); 522 pass &= !zen; 523 } 524 } catch (JSONException e) { 525 pass = false; 526 Log.e(TAG, "failed to unpack data from mocklistener", e); 527 } 528 } 529 pass &= found.size() >= 3; 530 status = pass ? PASS : FAIL; 531 } 532 533 @Override tearDown()534 protected void tearDown() { 535 mNm.cancelAll(); 536 deleteChannels(); 537 MockListener.getInstance().resetData(); 538 } 539 } 540 541 protected class PriorityInterceptsAlarmsTest extends InteractiveTestCase { 542 @Override inflate(ViewGroup parent)543 protected View inflate(ViewGroup parent) { 544 return createAutoItem(parent, R.string.attention_some_are_filtered_alarms); 545 } 546 547 @Override setUp()548 protected void setUp() { 549 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY); 550 NotificationManager.Policy policy = mNm.getNotificationPolicy(); 551 policy = new NotificationManager.Policy( 552 NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS, 553 policy.priorityCallSenders, 554 policy.priorityMessageSenders); 555 mNm.setNotificationPolicy(policy); 556 createChannels(); 557 // Event to Alice, Alarm to Bob, Reminder to Charlie: 558 sendEventAlarmReminderNotifications(SEND_ALL); 559 status = READY; 560 } 561 562 @Override test()563 protected void test() { 564 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 565 566 Set<String> found = new HashSet<String>(); 567 if (result.size() == 0) { 568 status = FAIL; 569 return; 570 } 571 boolean pass = true; 572 for (JSONObject payload : result) { 573 try { 574 String tag = payload.getString(JSON_TAG); 575 boolean zenIntercepted = !payload.getBoolean(JSON_MATCHES_ZEN_FILTER); 576 Log.e(TAG, tag + (zenIntercepted ? "" : " not") + " intercepted"); 577 if (found.contains(tag)) { 578 // multiple entries for same notification! 579 pass = false; 580 } else if (ALICE.equals(tag)) { 581 found.add(ALICE); 582 pass &= zenIntercepted; // Alice's event notif should be intercepted 583 } else if (BOB.equals(tag)) { 584 found.add(BOB); 585 pass &= !zenIntercepted; // Bob's alarm notif should not be intercepted 586 } else if (CHARLIE.equals(tag)) { 587 found.add(CHARLIE); 588 pass &= zenIntercepted; // Charlie's reminder notif should be intercepted 589 } 590 } catch (JSONException e) { 591 pass = false; 592 Log.e(TAG, "failed to unpack data from mocklistener", e); 593 } 594 } 595 pass &= found.size() >= 3; 596 status = pass ? PASS : FAIL; 597 } 598 599 @Override tearDown()600 protected void tearDown() { 601 mNm.cancelAll(); 602 deleteChannels(); 603 MockListener.getInstance().resetData(); 604 } 605 } 606 607 protected class PriorityInterceptsMediaSystemOtherTest extends InteractiveTestCase { 608 @Override inflate(ViewGroup parent)609 protected View inflate(ViewGroup parent) { 610 return createAutoItem(parent, R.string.attention_some_are_filtered_media_system_other); 611 } 612 613 @Override setUp()614 protected void setUp() { 615 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY); 616 NotificationManager.Policy policy = mNm.getNotificationPolicy(); 617 policy = new NotificationManager.Policy( 618 NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, 619 policy.priorityCallSenders, 620 policy.priorityMessageSenders); 621 mNm.setNotificationPolicy(policy); 622 createChannels(); 623 // Alarm to Alice, Other (Game) to Bob, Media to Charlie: 624 sendAlarmOtherMediaNotifications(SEND_ALL); 625 status = READY; 626 } 627 628 @Override test()629 protected void test() { 630 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 631 632 Set<String> found = new HashSet<String>(); 633 if (result.size() == 0) { 634 status = FAIL; 635 return; 636 } 637 boolean pass = true; 638 for (JSONObject payload : result) { 639 try { 640 String tag = payload.getString(JSON_TAG); 641 boolean zenIntercepted = !payload.getBoolean(JSON_MATCHES_ZEN_FILTER); 642 Log.e(TAG, tag + (zenIntercepted ? "" : " not") + " intercepted"); 643 if (found.contains(tag)) { 644 // multiple entries for same notification! 645 pass = false; 646 } else if (ALICE.equals(tag)) { 647 found.add(ALICE); 648 pass &= zenIntercepted; 649 } else if (BOB.equals(tag)) { 650 found.add(BOB); 651 pass &= !zenIntercepted; 652 } else if (CHARLIE.equals(tag)) { 653 found.add(CHARLIE); 654 pass &= !zenIntercepted; 655 } 656 } catch (JSONException e) { 657 pass = false; 658 Log.e(TAG, "failed to unpack data from mocklistener", e); 659 } 660 } 661 pass &= found.size() >= 3; 662 status = pass ? PASS : FAIL; 663 } 664 665 @Override tearDown()666 protected void tearDown() { 667 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL); 668 mNm.cancelAll(); 669 deleteChannels(); 670 MockListener.getInstance().resetData(); 671 } 672 } 673 674 // ordered by time: C, B, A 675 protected class DefaultOrderTest extends InteractiveTestCase { 676 @Override inflate(ViewGroup parent)677 protected View inflate(ViewGroup parent) { 678 return createAutoItem(parent, R.string.attention_default_order); 679 } 680 681 @Override setUp()682 protected void setUp() { 683 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL); 684 createChannels(); 685 sendNotifications(MODE_NONE, false, false); 686 status = READY; 687 } 688 689 @Override test()690 protected void test() { 691 List<String> orderedKeys = new ArrayList<>(MockListener.getInstance().mOrder); 692 int rankA = findTagInKeys(ALICE, orderedKeys); 693 int rankB = findTagInKeys(BOB, orderedKeys); 694 int rankC = findTagInKeys(CHARLIE, orderedKeys); 695 if (rankC < rankB && rankB < rankA) { 696 status = PASS; 697 } else { 698 logFail(rankA + ", " + rankB + ", " + rankC); 699 status = FAIL; 700 } 701 } 702 703 @Override tearDown()704 protected void tearDown() { 705 mNm.cancelAll(); 706 deleteChannels(); 707 MockListener.getInstance().resetData(); 708 } 709 } 710 711 // ordered by priority: B, C, A 712 protected class PriorityOrderTest extends InteractiveTestCase { 713 @Override inflate(ViewGroup parent)714 protected View inflate(ViewGroup parent) { 715 return createAutoItem(parent, R.string.attention_priority_order); 716 } 717 718 @Override setUp()719 protected void setUp() { 720 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL); 721 createChannels(); 722 sendNotifications(MODE_NONE, true, false); 723 status = READY; 724 } 725 726 @Override test()727 protected void test() { 728 List<String> orderedKeys = new ArrayList<>(MockListener.getInstance().mOrder); 729 int rankA = findTagInKeys(ALICE, orderedKeys); 730 int rankB = findTagInKeys(BOB, orderedKeys); 731 int rankC = findTagInKeys(CHARLIE, orderedKeys); 732 if (rankB < rankC && rankC < rankA) { 733 status = PASS; 734 } else { 735 logFail(rankA + ", " + rankB + ", " + rankC); 736 status = FAIL; 737 } 738 } 739 740 @Override tearDown()741 protected void tearDown() { 742 mNm.cancelAll(); 743 deleteChannels(); 744 MockListener.getInstance().resetData(); 745 } 746 } 747 748 // A starts at the top then falls to the bottom 749 protected class InterruptionOrderTest extends InteractiveTestCase { 750 boolean mSawElevation = false; 751 752 @Override inflate(ViewGroup parent)753 protected View inflate(ViewGroup parent) { 754 return createAutoItem(parent, R.string.attention_interruption_order); 755 } 756 757 @Override setUp()758 protected void setUp() { 759 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL); 760 delayTime = 15000; 761 createChannels(); 762 // send B & C noisy with contact affinity 763 sendNotifications(SEND_B, MODE_URI, false, true); 764 sleep(1000); 765 sendNotifications(SEND_C, MODE_URI, false, true); 766 status = READY_AFTER_LONG_DELAY; 767 } 768 769 @Override test()770 protected void test() { 771 if (status == READY_AFTER_LONG_DELAY) { 772 // send A noisy but no contact affinity 773 sendNotifications(SEND_A, MODE_NONE, false, true); 774 status = RETEST; 775 } else if (status == RETEST || status == RETEST_AFTER_LONG_DELAY) { 776 List<String> orderedKeys = new ArrayList<>(MockListener.getInstance().mOrder); 777 int rankA = findTagInKeys(ALICE, orderedKeys); 778 int rankB = findTagInKeys(BOB, orderedKeys); 779 int rankC = findTagInKeys(CHARLIE, orderedKeys); 780 if (!mSawElevation) { 781 if (rankA < rankB && rankA < rankC) { 782 mSawElevation = true; 783 status = RETEST_AFTER_LONG_DELAY; 784 } else { 785 logFail("noisy notification did not sort to top."); 786 status = FAIL; 787 } 788 } else { 789 if (rankA > rankB && rankA > rankC) { 790 status = PASS; 791 } else { 792 logFail("noisy notification did not fade back into the list."); 793 status = FAIL; 794 } 795 } 796 } 797 } 798 799 @Override tearDown()800 protected void tearDown() { 801 mNm.cancelAll(); 802 deleteChannels(); 803 MockListener.getInstance().resetData(); 804 } 805 } 806 807 // B & C above the fold, A below 808 protected class AmbientBitsTest extends InteractiveTestCase { 809 @Override inflate(ViewGroup parent)810 protected View inflate(ViewGroup parent) { 811 return createAutoItem(parent, R.string.attention_ambient_bit); 812 } 813 814 @Override setUp()815 protected void setUp() { 816 createChannels(); 817 sendNotifications(SEND_B | SEND_C, MODE_NONE, true, true); 818 sendNotifications(SEND_A, MODE_NONE, true, false); 819 status = READY; 820 } 821 822 @Override test()823 protected void test() { 824 List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted()); 825 826 Set<String> found = new HashSet<String>(); 827 if (result.size() == 0) { 828 status = FAIL; 829 return; 830 } 831 boolean pass = true; 832 for (JSONObject payload : result) { 833 try { 834 String tag = payload.getString(JSON_TAG); 835 boolean ambient = payload.getBoolean(JSON_AMBIENT); 836 Log.e(TAG, tag + (ambient ? " is" : " isn't") + " ambient"); 837 if (found.contains(tag)) { 838 // multiple entries for same notification! 839 pass = false; 840 } else if (ALICE.equals(tag)) { 841 found.add(ALICE); 842 pass &= ambient; 843 } else if (BOB.equals(tag)) { 844 found.add(BOB); 845 pass &= !ambient; 846 } else if (CHARLIE.equals(tag)) { 847 found.add(CHARLIE); 848 pass &= !ambient; 849 } 850 } catch (JSONException e) { 851 pass = false; 852 Log.e(TAG, "failed to unpack data from mocklistener", e); 853 } 854 } 855 pass &= found.size() == 3; 856 status = pass ? PASS : FAIL; 857 } 858 859 @Override tearDown()860 protected void tearDown() { 861 mNm.cancelAll(); 862 deleteChannels(); 863 MockListener.getInstance().resetData(); 864 } 865 } 866 867 // ordered by contact affinity: A, B, C 868 protected class LookupUriOrderTest extends InteractiveTestCase { 869 @Override inflate(ViewGroup parent)870 protected View inflate(ViewGroup parent) { 871 return createAutoItem(parent, R.string.attention_lookup_order); 872 } 873 874 @Override setUp()875 protected void setUp() { 876 createChannels(); 877 sendNotifications(MODE_URI, false, false); 878 status = READY; 879 } 880 881 @Override test()882 protected void test() { 883 List<String> orderedKeys = new ArrayList<>(MockListener.getInstance().mOrder); 884 int rankA = findTagInKeys(ALICE, orderedKeys); 885 int rankB = findTagInKeys(BOB, orderedKeys); 886 int rankC = findTagInKeys(CHARLIE, orderedKeys); 887 if (rankA < rankB && rankB < rankC) { 888 status = PASS; 889 } else { 890 logFail(rankA + ", " + rankB + ", " + rankC); 891 status = FAIL; 892 } 893 } 894 895 @Override tearDown()896 protected void tearDown() { 897 mNm.cancelAll(); 898 deleteChannels(); 899 MockListener.getInstance().resetData(); 900 } 901 } 902 903 // ordered by contact affinity: A, B, C 904 protected class EmailOrderTest extends InteractiveTestCase { 905 @Override inflate(ViewGroup parent)906 protected View inflate(ViewGroup parent) { 907 return createAutoItem(parent, R.string.attention_email_order); 908 } 909 910 @Override setUp()911 protected void setUp() { 912 createChannels(); 913 sendNotifications(MODE_EMAIL, false, false); 914 status = READY; 915 } 916 917 @Override test()918 protected void test() { 919 List<String> orderedKeys = new ArrayList<>(MockListener.getInstance().mOrder); 920 int rankA = findTagInKeys(ALICE, orderedKeys); 921 int rankB = findTagInKeys(BOB, orderedKeys); 922 int rankC = findTagInKeys(CHARLIE, orderedKeys); 923 if (rankA < rankB && rankB < rankC) { 924 status = PASS; 925 } else { 926 logFail(rankA + ", " + rankB + ", " + rankC); 927 status = FAIL; 928 } 929 } 930 931 @Override tearDown()932 protected void tearDown() { 933 mNm.cancelAll(); 934 deleteChannels(); 935 MockListener.getInstance().resetData(); 936 } 937 } 938 939 // ordered by contact affinity: A, B, C 940 protected class PhoneOrderTest extends InteractiveTestCase { 941 @Override inflate(ViewGroup parent)942 protected View inflate(ViewGroup parent) { 943 return createAutoItem(parent, R.string.attention_phone_order); 944 } 945 946 @Override setUp()947 protected void setUp() { 948 createChannels(); 949 sendNotifications(MODE_PHONE, false, false); 950 status = READY; 951 } 952 953 @Override test()954 protected void test() { 955 List<String> orderedKeys = new ArrayList<>(MockListener.getInstance().mOrder); 956 int rankA = findTagInKeys(ALICE, orderedKeys); 957 int rankB = findTagInKeys(BOB, orderedKeys); 958 int rankC = findTagInKeys(CHARLIE, orderedKeys); 959 if (rankA < rankB && rankB < rankC) { 960 status = PASS; 961 } else { 962 logFail(rankA + ", " + rankB + ", " + rankC); 963 status = FAIL; 964 } 965 } 966 967 @Override tearDown()968 protected void tearDown() { 969 mNm.cancelAll(); 970 deleteChannels(); 971 MockListener.getInstance().resetData(); 972 } 973 } 974 975 // Utilities 976 977 // usePriorities true: B, C, A 978 // usePriorities false: 979 // MODE_NONE: C, B, A 980 // otherwise: A, B ,C sendNotifications(int annotationMode, boolean uriMode, boolean noisy)981 private void sendNotifications(int annotationMode, boolean uriMode, boolean noisy) { 982 sendNotifications(SEND_ALL, annotationMode, uriMode, noisy); 983 } 984 sendNotifications(int which, int uriMode, boolean usePriorities, boolean noisy)985 private void sendNotifications(int which, int uriMode, boolean usePriorities, boolean noisy) { 986 // C, B, A when sorted by time. Times must be in the past 987 long whenA = System.currentTimeMillis() - 4000000L; 988 long whenB = System.currentTimeMillis() - 2000000L; 989 long whenC = System.currentTimeMillis() - 1000000L; 990 991 // B, C, A when sorted by priorities 992 int priorityA = usePriorities ? Notification.PRIORITY_MIN : Notification.PRIORITY_DEFAULT; 993 int priorityB = usePriorities ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT; 994 int priorityC = usePriorities ? Notification.PRIORITY_LOW : Notification.PRIORITY_DEFAULT; 995 996 final String channelId = noisy ? NOTIFICATION_CHANNEL_ID_NOISY : NOTIFICATION_CHANNEL_ID; 997 998 if ((which & SEND_B) != 0) { 999 Notification.Builder bob = new Notification.Builder(mContext, channelId) 1000 .setContentTitle(BOB) 1001 .setContentText(BOB) 1002 .setSmallIcon(R.drawable.ic_stat_bob) 1003 .setPriority(priorityB) 1004 .setCategory(Notification.CATEGORY_MESSAGE) 1005 .setWhen(whenB); 1006 addPerson(uriMode, bob, mBobUri, BOB_PHONE, BOB_EMAIL); 1007 mNm.notify(BOB, NOTIFICATION_ID + 2, bob.build()); 1008 } 1009 if ((which & SEND_C) != 0) { 1010 Notification.Builder charlie = 1011 new Notification.Builder(mContext, channelId) 1012 .setContentTitle(CHARLIE) 1013 .setContentText(CHARLIE) 1014 .setSmallIcon(R.drawable.ic_stat_charlie) 1015 .setPriority(priorityC) 1016 .setCategory(Notification.CATEGORY_MESSAGE) 1017 .setWhen(whenC); 1018 addPerson(uriMode, charlie, mCharlieUri, CHARLIE_PHONE, CHARLIE_EMAIL); 1019 mNm.notify(CHARLIE, NOTIFICATION_ID + 3, charlie.build()); 1020 } 1021 if ((which & SEND_A) != 0) { 1022 Notification.Builder alice = new Notification.Builder(mContext, channelId) 1023 .setContentTitle(ALICE) 1024 .setContentText(ALICE) 1025 .setSmallIcon(R.drawable.ic_stat_alice) 1026 .setPriority(priorityA) 1027 .setCategory(Notification.CATEGORY_MESSAGE) 1028 .setWhen(whenA); 1029 addPerson(uriMode, alice, mAliceUri, ALICE_PHONE, ALICE_EMAIL); 1030 mNm.notify(ALICE, NOTIFICATION_ID + 1, alice.build()); 1031 } 1032 } 1033 sendEventAlarmReminderNotifications(int which)1034 private void sendEventAlarmReminderNotifications(int which) { 1035 long when = System.currentTimeMillis() - 4000000L; 1036 final String channelId = NOTIFICATION_CHANNEL_ID; 1037 1038 // Event notification to Alice 1039 if ((which & SEND_A) != 0) { 1040 Notification.Builder alice = new Notification.Builder(mContext, channelId) 1041 .setContentTitle(ALICE) 1042 .setContentText(ALICE) 1043 .setSmallIcon(R.drawable.ic_stat_alice) 1044 .setCategory(Notification.CATEGORY_EVENT) 1045 .setWhen(when); 1046 mNm.notify(ALICE, NOTIFICATION_ID + 1, alice.build()); 1047 } 1048 1049 // Alarm notification to Bob 1050 if ((which & SEND_B) != 0) { 1051 Notification.Builder bob = new Notification.Builder(mContext, channelId) 1052 .setContentTitle(BOB) 1053 .setContentText(BOB) 1054 .setSmallIcon(R.drawable.ic_stat_bob) 1055 .setCategory(Notification.CATEGORY_ALARM) 1056 .setWhen(when); 1057 mNm.notify(BOB, NOTIFICATION_ID + 2, bob.build()); 1058 } 1059 1060 // Reminder notification to Charlie 1061 if ((which & SEND_C) != 0) { 1062 Notification.Builder charlie = 1063 new Notification.Builder(mContext, channelId) 1064 .setContentTitle(CHARLIE) 1065 .setContentText(CHARLIE) 1066 .setSmallIcon(R.drawable.ic_stat_charlie) 1067 .setCategory(Notification.CATEGORY_REMINDER) 1068 .setWhen(when); 1069 mNm.notify(CHARLIE, NOTIFICATION_ID + 3, charlie.build()); 1070 } 1071 } 1072 sendAlarmOtherMediaNotifications(int which)1073 private void sendAlarmOtherMediaNotifications(int which) { 1074 long when = System.currentTimeMillis() - 4000000L; 1075 final String channelId = NOTIFICATION_CHANNEL_ID; 1076 1077 // Alarm notification to Alice 1078 if ((which & SEND_A) != 0) { 1079 Notification.Builder alice = new Notification.Builder(mContext, channelId) 1080 .setContentTitle(ALICE) 1081 .setContentText(ALICE) 1082 .setSmallIcon(R.drawable.ic_stat_alice) 1083 .setCategory(Notification.CATEGORY_ALARM) 1084 .setWhen(when); 1085 mNm.notify(ALICE, NOTIFICATION_ID + 1, alice.build()); 1086 } 1087 1088 // "Other" notification to Bob 1089 if ((which & SEND_B) != 0) { 1090 Notification.Builder bob = new Notification.Builder(mContext, 1091 NOTIFICATION_CHANNEL_ID_GAME) 1092 .setContentTitle(BOB) 1093 .setContentText(BOB) 1094 .setSmallIcon(R.drawable.ic_stat_bob) 1095 .setWhen(when); 1096 mNm.notify(BOB, NOTIFICATION_ID + 2, bob.build()); 1097 } 1098 1099 // Media notification to Charlie 1100 if ((which & SEND_C) != 0) { 1101 Notification.Builder charlie = 1102 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID_MEDIA) 1103 .setContentTitle(CHARLIE) 1104 .setContentText(CHARLIE) 1105 .setSmallIcon(R.drawable.ic_stat_charlie) 1106 .setWhen(when); 1107 mNm.notify(CHARLIE, NOTIFICATION_ID + 3, charlie.build()); 1108 } 1109 } 1110 addPerson(int mode, Notification.Builder note, Uri uri, String phone, String email)1111 private void addPerson(int mode, Notification.Builder note, 1112 Uri uri, String phone, String email) { 1113 if (mode == MODE_URI && uri != null) { 1114 note.addPerson(uri.toString()); 1115 } else if (mode == MODE_PHONE) { 1116 note.addPerson(Uri.fromParts("tel", phone, null).toString()); 1117 } else if (mode == MODE_EMAIL) { 1118 note.addPerson(Uri.fromParts("mailto", email, null).toString()); 1119 } 1120 } 1121 insertSingleContact(String name, String phone, String email, boolean starred)1122 private void insertSingleContact(String name, String phone, String email, boolean starred) { 1123 final ArrayList<ContentProviderOperation> operationList = 1124 new ArrayList<ContentProviderOperation>(); 1125 ContentProviderOperation.Builder builder = 1126 ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI); 1127 builder.withValue(ContactsContract.RawContacts.STARRED, starred ? 1 : 0); 1128 operationList.add(builder.build()); 1129 1130 builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI); 1131 builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0); 1132 builder.withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); 1133 builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name); 1134 operationList.add(builder.build()); 1135 1136 if (phone != null) { 1137 builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI); 1138 builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0); 1139 builder.withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 1140 builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE); 1141 builder.withValue(Phone.NUMBER, phone); 1142 builder.withValue(ContactsContract.Data.IS_PRIMARY, 1); 1143 operationList.add(builder.build()); 1144 } 1145 if (email != null) { 1146 builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI); 1147 builder.withValueBackReference(Email.RAW_CONTACT_ID, 0); 1148 builder.withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 1149 builder.withValue(Email.TYPE, Email.TYPE_HOME); 1150 builder.withValue(Email.DATA, email); 1151 operationList.add(builder.build()); 1152 } 1153 1154 try { 1155 mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList); 1156 } catch (RemoteException e) { 1157 Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); 1158 } catch (OperationApplicationException e) { 1159 Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); 1160 } 1161 } 1162 lookupContact(String phone)1163 private Uri lookupContact(String phone) { 1164 Cursor c = null; 1165 try { 1166 Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, 1167 Uri.encode(phone)); 1168 String[] projection = new String[] { ContactsContract.Contacts._ID, 1169 ContactsContract.Contacts.LOOKUP_KEY }; 1170 c = mContext.getContentResolver().query(phoneUri, projection, null, null, null); 1171 if (c != null && c.getCount() > 0) { 1172 c.moveToFirst(); 1173 int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY); 1174 int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID); 1175 String lookupKey = c.getString(lookupIdx); 1176 long contactId = c.getLong(idIdx); 1177 return ContactsContract.Contacts.getLookupUri(contactId, lookupKey); 1178 } 1179 } catch (Throwable t) { 1180 Log.w(TAG, "Problem getting content resolver or performing contacts query.", t); 1181 } finally { 1182 if (c != null) { 1183 c.close(); 1184 } 1185 } 1186 return null; 1187 } 1188 isStarred(Uri uri)1189 private boolean isStarred(Uri uri) { 1190 Cursor c = null; 1191 boolean starred = false; 1192 try { 1193 String[] projection = new String[] { ContactsContract.Contacts.STARRED }; 1194 c = mContext.getContentResolver().query(uri, projection, null, null, null); 1195 if (c != null && c.getCount() > 0) { 1196 int starredIdx = c.getColumnIndex(ContactsContract.Contacts.STARRED); 1197 while (c.moveToNext()) { 1198 starred |= c.getInt(starredIdx) == 1; 1199 } 1200 } 1201 } catch (Throwable t) { 1202 Log.w(TAG, "Problem getting content resolver or performing contacts query.", t); 1203 } finally { 1204 if (c != null) { 1205 c.close(); 1206 } 1207 } 1208 return starred; 1209 } 1210 1211 /** Search a list of notification keys for a givcen tag. */ findTagInKeys(String tag, List<String> orderedKeys)1212 private int findTagInKeys(String tag, List<String> orderedKeys) { 1213 for (int i = 0; i < orderedKeys.size(); i++) { 1214 if (orderedKeys.get(i).contains(tag)) { 1215 return i; 1216 } 1217 } 1218 return -1; 1219 } 1220 } 1221