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