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.content.ContentProviderOperation; 25 import android.content.OperationApplicationException; 26 import android.database.Cursor; 27 import android.net.Uri; 28 import android.os.RemoteException; 29 import android.provider.ContactsContract; 30 import android.provider.ContactsContract.CommonDataKinds.Email; 31 import android.provider.ContactsContract.CommonDataKinds.Phone; 32 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 33 import android.service.notification.NotificationListenerService; 34 import android.util.Log; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import com.android.cts.verifier.R; 38 import org.json.JSONException; 39 import org.json.JSONObject; 40 41 import java.util.ArrayList; 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Set; 45 46 public class AttentionManagementVerifierActivity 47 extends InteractiveVerifierActivity { 48 private static final String TAG = "NoListenerAttentionVerifier"; 49 50 private static final String ALICE = "Alice"; 51 private static final String ALICE_PHONE = "+16175551212"; 52 private static final String ALICE_EMAIL = "alice (at) _foo._bar"; 53 private static final String BOB = "Bob"; 54 private static final String BOB_PHONE = "+16505551212";; 55 private static final String BOB_EMAIL = "bob (at) _foo._bar"; 56 private static final String CHARLIE = "Charlie"; 57 private static final String CHARLIE_PHONE = "+13305551212"; 58 private static final String CHARLIE_EMAIL = "charlie (at) _foo._bar"; 59 private static final int MODE_NONE = 0; 60 private static final int MODE_URI = 1; 61 private static final int MODE_PHONE = 2; 62 private static final int MODE_EMAIL = 3; 63 64 private Uri mAliceUri; 65 private Uri mBobUri; 66 private Uri mCharlieUri; 67 68 @Override 69 int getTitleResource() { 70 return R.string.attention_test; 71 } 72 73 @Override 74 int getInstructionsResource() { 75 return R.string.attention_info; 76 } 77 78 // Test Setup 79 80 @Override 81 protected List<InteractiveTestCase> createTestItems() { 82 List<InteractiveTestCase> tests = new ArrayList<>(17); 83 tests.add(new IsEnabledTest()); 84 tests.add(new ServiceStartedTest()); 85 tests.add(new InsertContactsTest()); 86 tests.add(new SetModeNoneTest()); 87 tests.add(new NoneInterceptsAllTest()); 88 tests.add(new SetModePriorityTest()); 89 tests.add(new PriorityInterceptsSomeTest()); 90 tests.add(new SetModeAllTest()); 91 tests.add(new AllInterceptsNothingTest()); 92 tests.add(new DefaultOrderTest()); 93 tests.add(new PrioritytOrderTest()); 94 tests.add(new InterruptionOrderTest()); 95 tests.add(new AmbientBitsTest()); 96 tests.add(new LookupUriOrderTest()); 97 tests.add(new EmailOrderTest()); 98 tests.add(new PhoneOrderTest()); 99 tests.add(new DeleteContactsTest()); 100 return tests; 101 } 102 103 // Tests 104 105 protected class InsertContactsTest extends InteractiveTestCase { 106 @Override 107 View inflate(ViewGroup parent) { 108 return createAutoItem(parent, R.string.attention_create_contacts); 109 } 110 111 @Override 112 void setUp() { 113 insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, true); 114 insertSingleContact(BOB, BOB_PHONE, BOB_EMAIL, false); 115 // charlie is not in contacts 116 status = READY; 117 // wait for insertions to move through the system 118 delay(); 119 } 120 121 @Override 122 void test() { 123 mAliceUri = lookupContact(ALICE_PHONE); 124 mBobUri = lookupContact(BOB_PHONE); 125 mCharlieUri = lookupContact(CHARLIE_PHONE); 126 127 status = PASS; 128 if (mAliceUri == null) { status = FAIL; } 129 if (mBobUri == null) { status = FAIL; } 130 if (mCharlieUri != null) { status = FAIL; } 131 next(); 132 } 133 } 134 135 protected class DeleteContactsTest extends InteractiveTestCase { 136 @Override 137 View inflate(ViewGroup parent) { 138 return createAutoItem(parent, R.string.attention_delete_contacts); 139 } 140 141 @Override 142 void test() { 143 final ArrayList<ContentProviderOperation> operationList = new ArrayList<>(); 144 operationList.add(ContentProviderOperation.newDelete(mAliceUri).build()); 145 operationList.add(ContentProviderOperation.newDelete(mBobUri).build()); 146 try { 147 mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList); 148 status = READY; 149 } catch (RemoteException e) { 150 Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); 151 status = FAIL; 152 } catch (OperationApplicationException e) { 153 Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); 154 status = FAIL; 155 } 156 status = PASS; 157 next(); 158 } 159 } 160 161 protected class SetModeNoneTest extends InteractiveTestCase { 162 @Override 163 View inflate(ViewGroup parent) { 164 return createRetryItem(parent, R.string.attention_filter_none); 165 } 166 167 @Override 168 void test() { 169 MockListener.probeFilter(mContext, 170 new MockListener.IntegerResultCatcher() { 171 @Override 172 public void accept(int mode) { 173 if (mode == NotificationListenerService.INTERRUPTION_FILTER_NONE) { 174 status = PASS; 175 next(); 176 } else { 177 Log.i("SetModeNoneTest", "waiting, current mode is: " + mode); 178 status = WAIT_FOR_USER; 179 } 180 } 181 }); 182 } 183 184 @Override 185 void tearDown() { 186 mNm.cancelAll(); 187 MockListener.resetListenerData(mContext); 188 delay(); 189 } 190 } 191 192 protected class NoneInterceptsAllTest extends InteractiveTestCase { 193 @Override 194 View inflate(ViewGroup parent) { 195 return createAutoItem(parent, R.string.attention_all_are_filtered); 196 } 197 198 @Override 199 void setUp() { 200 sendNotifications(MODE_URI, false, false); 201 status = READY; 202 // wait for notifications to move through the system 203 delay(); 204 } 205 206 @Override 207 void test() { 208 MockListener.probeListenerPayloads(mContext, 209 new MockListener.StringListResultCatcher() { 210 @Override 211 public void accept(List<String> result) { 212 Set<String> found = new HashSet<String>(); 213 if (result == null || result.size() == 0) { 214 status = FAIL; 215 next(); 216 return; 217 } 218 boolean pass = true; 219 for (String payloadData : result) { 220 try { 221 JSONObject payload = new JSONObject(payloadData); 222 String tag = payload.getString(JSON_TAG); 223 boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER); 224 Log.e(TAG, tag + (zen ? "" : " not") + " intercepted"); 225 if (found.contains(tag)) { 226 // multiple entries for same notification! 227 pass = false; 228 } else if (ALICE.equals(tag)) { 229 found.add(ALICE); 230 pass &= !zen; 231 } else if (BOB.equals(tag)) { 232 found.add(BOB); 233 pass &= !zen; 234 } else if (CHARLIE.equals(tag)) { 235 found.add(CHARLIE); 236 pass &= !zen; 237 } 238 } catch (JSONException e) { 239 pass = false; 240 Log.e(TAG, "failed to unpack data from mocklistener", e); 241 } 242 } 243 pass &= found.size() == 3; 244 status = pass ? PASS : FAIL; 245 next(); 246 } 247 }); 248 delay(); // in case the catcher never returns 249 } 250 251 @Override 252 void tearDown() { 253 mNm.cancelAll(); 254 MockListener.resetListenerData(mContext); 255 delay(); 256 } 257 258 } 259 260 protected class SetModeAllTest extends InteractiveTestCase { 261 @Override 262 View inflate(ViewGroup parent) { 263 return createRetryItem(parent, R.string.attention_filter_all); 264 } 265 266 @Override 267 void test() { 268 MockListener.probeFilter(mContext, 269 new MockListener.IntegerResultCatcher() { 270 @Override 271 public void accept(int mode) { 272 if (mode == NotificationListenerService.INTERRUPTION_FILTER_ALL) { 273 status = PASS; 274 next(); 275 } else { 276 Log.i("SetModeAllTest", "waiting, current mode is: " + mode); 277 status = WAIT_FOR_USER; 278 } 279 } 280 }); 281 } 282 } 283 284 protected class AllInterceptsNothingTest extends InteractiveTestCase { 285 @Override 286 View inflate(ViewGroup parent) { 287 return createAutoItem(parent, R.string.attention_none_are_filtered); 288 } 289 290 @Override 291 void setUp() { 292 sendNotifications(MODE_URI, false, false); 293 status = READY; 294 // wait for notifications to move through the system 295 delay(); 296 } 297 298 @Override 299 void test() { 300 MockListener.probeListenerPayloads(mContext, 301 new MockListener.StringListResultCatcher() { 302 @Override 303 public void accept(List<String> result) { 304 Set<String> found = new HashSet<String>(); 305 if (result == null || result.size() == 0) { 306 status = FAIL; 307 return; 308 } 309 boolean pass = true; 310 for (String payloadData : result) { 311 try { 312 JSONObject payload = new JSONObject(payloadData); 313 String tag = payload.getString(JSON_TAG); 314 boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER); 315 Log.e(TAG, tag + (zen ? "" : " not") + " intercepted"); 316 if (found.contains(tag)) { 317 // multiple entries for same notification! 318 pass = false; 319 } else if (ALICE.equals(tag)) { 320 found.add(ALICE); 321 pass &= zen; 322 } else if (BOB.equals(tag)) { 323 found.add(BOB); 324 pass &= zen; 325 } else if (CHARLIE.equals(tag)) { 326 found.add(CHARLIE); 327 pass &= zen; 328 } 329 } catch (JSONException e) { 330 pass = false; 331 Log.e(TAG, "failed to unpack data from mocklistener", e); 332 } 333 } 334 pass &= found.size() == 3; 335 status = pass ? PASS : FAIL; 336 next(); 337 } 338 }); 339 delay(); // in case the catcher never returns 340 } 341 342 @Override 343 void tearDown() { 344 mNm.cancelAll(); 345 MockListener.resetListenerData(mContext); 346 delay(); 347 } 348 } 349 350 protected class SetModePriorityTest extends InteractiveTestCase { 351 @Override 352 View inflate(ViewGroup parent) { 353 return createRetryItem(parent, R.string.attention_filter_priority); 354 } 355 356 @Override 357 void test() { 358 MockListener.probeFilter(mContext, 359 new MockListener.IntegerResultCatcher() { 360 @Override 361 public void accept(int mode) { 362 if (mode == NotificationListenerService.INTERRUPTION_FILTER_PRIORITY) { 363 status = PASS; 364 next(); 365 } else { 366 Log.i("SetModePriorityTest", "waiting, current mode is: " + mode); 367 status = WAIT_FOR_USER; 368 } 369 } 370 }); 371 } 372 } 373 374 protected class PriorityInterceptsSomeTest extends InteractiveTestCase { 375 @Override 376 View inflate(ViewGroup parent) { 377 return createAutoItem(parent, R.string.attention_some_are_filtered); 378 } 379 380 @Override 381 void setUp() { 382 sendNotifications(MODE_URI, false, false); 383 status = READY; 384 // wait for notifications to move through the system 385 delay(); 386 } 387 388 @Override 389 void test() { 390 MockListener.probeListenerPayloads(mContext, 391 new MockListener.StringListResultCatcher() { 392 @Override 393 public void accept(List<String> result) { 394 Set<String> found = new HashSet<String>(); 395 if (result == null || result.size() == 0) { 396 status = FAIL; 397 return; 398 } 399 boolean pass = true; 400 for (String payloadData : result) { 401 try { 402 JSONObject payload = new JSONObject(payloadData); 403 String tag = payload.getString(JSON_TAG); 404 boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER); 405 Log.e(TAG, tag + (zen ? "" : " not") + " intercepted"); 406 if (found.contains(tag)) { 407 // multiple entries for same notification! 408 pass = false; 409 } else if (ALICE.equals(tag)) { 410 found.add(ALICE); 411 pass &= zen; 412 } else if (BOB.equals(tag)) { 413 found.add(BOB); 414 pass &= !zen; 415 } else if (CHARLIE.equals(tag)) { 416 found.add(CHARLIE); 417 pass &= !zen; 418 } 419 } catch (JSONException e) { 420 pass = false; 421 Log.e(TAG, "failed to unpack data from mocklistener", e); 422 } 423 } 424 pass &= found.size() == 3; 425 status = pass ? PASS : FAIL; 426 next(); 427 } 428 }); 429 delay(); // in case the catcher never returns 430 } 431 432 @Override 433 void tearDown() { 434 mNm.cancelAll(); 435 MockListener.resetListenerData(mContext); 436 delay(); 437 } 438 } 439 440 // ordered by time: C, B, A 441 protected class DefaultOrderTest extends InteractiveTestCase { 442 @Override 443 View inflate(ViewGroup parent) { 444 return createAutoItem(parent, R.string.attention_default_order); 445 } 446 447 @Override 448 void setUp() { 449 sendNotifications(MODE_NONE, false, false); 450 status = READY; 451 // wait for notifications to move through the system 452 delay(); 453 } 454 455 @Override 456 void test() { 457 MockListener.probeListenerOrder(mContext, 458 new MockListener.StringListResultCatcher() { 459 @Override 460 public void accept(List<String> orderedKeys) { 461 int rankA = findTagInKeys(ALICE, orderedKeys); 462 int rankB = findTagInKeys(BOB, orderedKeys); 463 int rankC = findTagInKeys(CHARLIE, orderedKeys); 464 if (rankC < rankB && rankB < rankA) { 465 status = PASS; 466 } else { 467 logFail(rankA + ", " + rankB + ", " + rankC); 468 status = FAIL; 469 } 470 next(); 471 } 472 }); 473 delay(); // in case the catcher never returns 474 } 475 476 @Override 477 void tearDown() { 478 mNm.cancelAll(); 479 MockListener.resetListenerData(mContext); 480 delay(); 481 } 482 } 483 484 // ordered by priority: B, C, A 485 protected class PrioritytOrderTest extends InteractiveTestCase { 486 @Override 487 View inflate(ViewGroup parent) { 488 return createAutoItem(parent, R.string.attention_priority_order); 489 } 490 491 @Override 492 void setUp() { 493 sendNotifications(MODE_NONE, true, false); 494 status = READY; 495 // wait for notifications to move through the system 496 delay(); 497 } 498 499 @Override 500 void test() { 501 MockListener.probeListenerOrder(mContext, 502 new MockListener.StringListResultCatcher() { 503 @Override 504 public void accept(List<String> orderedKeys) { 505 int rankA = findTagInKeys(ALICE, orderedKeys); 506 int rankB = findTagInKeys(BOB, orderedKeys); 507 int rankC = findTagInKeys(CHARLIE, orderedKeys); 508 if (rankB < rankC && rankC < rankA) { 509 status = PASS; 510 } else { 511 logFail(rankA + ", " + rankB + ", " + rankC); 512 status = FAIL; 513 } 514 next(); 515 } 516 }); 517 delay(); // in case the catcher never returns 518 } 519 520 @Override 521 void tearDown() { 522 mNm.cancelAll(); 523 MockListener.resetListenerData(mContext); 524 delay(); 525 } 526 } 527 528 // A starts at the top then falls to the bottom 529 protected class InterruptionOrderTest extends InteractiveTestCase { 530 @Override 531 View inflate(ViewGroup parent) { 532 return createAutoItem(parent, R.string.attention_interruption_order); 533 } 534 535 @Override 536 void setUp() { 537 sendNotifications(MODE_NONE, false, true); 538 status = READY; 539 // wait for notifications to move through the system 540 delay(); 541 } 542 543 @Override 544 void test() { 545 if (status == READY) { 546 MockListener.probeListenerOrder(mContext, 547 new MockListener.StringListResultCatcher() { 548 @Override 549 public void accept(List<String> orderedKeys) { 550 int rankA = findTagInKeys(ALICE, orderedKeys); 551 int rankB = findTagInKeys(BOB, orderedKeys); 552 int rankC = findTagInKeys(CHARLIE, orderedKeys); 553 if (rankA < rankB && rankA < rankC) { 554 status = RETEST; 555 delay(12000); 556 } else { 557 logFail("noisy notification did not sort to top."); 558 status = FAIL; 559 next(); 560 } 561 } 562 }); 563 delay(); // in case the catcher never returns 564 } else { 565 MockListener.probeListenerOrder(mContext, 566 new MockListener.StringListResultCatcher() { 567 @Override 568 public void accept(List<String> orderedKeys) { 569 int rankA = findTagInKeys(ALICE, orderedKeys); 570 int rankB = findTagInKeys(BOB, orderedKeys); 571 int rankC = findTagInKeys(CHARLIE, orderedKeys); 572 if (rankA > rankB && rankA > rankC) { 573 status = PASS; 574 } else { 575 logFail("noisy notification did not fade back into the list."); 576 status = FAIL; 577 } 578 next(); 579 } 580 }); 581 delay(); // in case the catcher never returns 582 } 583 } 584 585 @Override 586 void tearDown() { 587 mNm.cancelAll(); 588 MockListener.resetListenerData(mContext); 589 delay(); 590 } 591 } 592 593 // B & C above the fold, A below 594 protected class AmbientBitsTest extends InteractiveTestCase { 595 @Override 596 View inflate(ViewGroup parent) { 597 return createAutoItem(parent, R.string.attention_ambient_bit); 598 } 599 600 @Override 601 void setUp() { 602 sendNotifications(MODE_NONE, true, false); 603 status = READY; 604 // wait for notifications to move through the system 605 delay(); 606 } 607 608 @Override 609 void test() { 610 MockListener.probeListenerPayloads(mContext, 611 new MockListener.StringListResultCatcher() { 612 @Override 613 public void accept(List<String> result) { 614 Set<String> found = new HashSet<String>(); 615 if (result == null || result.size() == 0) { 616 status = FAIL; 617 return; 618 } 619 boolean pass = true; 620 for (String payloadData : result) { 621 try { 622 JSONObject payload = new JSONObject(payloadData); 623 String tag = payload.getString(JSON_TAG); 624 boolean ambient = payload.getBoolean(JSON_AMBIENT); 625 Log.e(TAG, tag + (ambient ? " is" : " isn't") + " ambient"); 626 if (found.contains(tag)) { 627 // multiple entries for same notification! 628 pass = false; 629 } else if (ALICE.equals(tag)) { 630 found.add(ALICE); 631 pass &= ambient; 632 } else if (BOB.equals(tag)) { 633 found.add(BOB); 634 pass &= !ambient; 635 } else if (CHARLIE.equals(tag)) { 636 found.add(CHARLIE); 637 pass &= !ambient; 638 } 639 } catch (JSONException e) { 640 pass = false; 641 Log.e(TAG, "failed to unpack data from mocklistener", e); 642 } 643 } 644 pass &= found.size() == 3; 645 status = pass ? PASS : FAIL; 646 next(); 647 } 648 }); 649 delay(); // in case the catcher never returns 650 } 651 652 @Override 653 void tearDown() { 654 mNm.cancelAll(); 655 MockListener.resetListenerData(mContext); 656 delay(); 657 } 658 } 659 660 // ordered by contact affinity: A, B, C 661 protected class LookupUriOrderTest extends InteractiveTestCase { 662 @Override 663 View inflate(ViewGroup parent) { 664 return createAutoItem(parent, R.string.attention_lookup_order); 665 } 666 667 @Override 668 void setUp() { 669 sendNotifications(MODE_URI, false, false); 670 status = READY; 671 // wait for notifications to move through the system 672 delay(); 673 } 674 675 @Override 676 void test() { 677 MockListener.probeListenerOrder(mContext, 678 new MockListener.StringListResultCatcher() { 679 @Override 680 public void accept(List<String> orderedKeys) { 681 int rankA = findTagInKeys(ALICE, orderedKeys); 682 int rankB = findTagInKeys(BOB, orderedKeys); 683 int rankC = findTagInKeys(CHARLIE, orderedKeys); 684 if (rankA < rankB && rankB < rankC) { 685 status = PASS; 686 } else { 687 logFail(rankA + ", " + rankB + ", " + rankC); 688 status = FAIL; 689 } 690 next(); 691 } 692 }); 693 delay(); // in case the catcher never returns 694 } 695 696 @Override 697 void tearDown() { 698 mNm.cancelAll(); 699 MockListener.resetListenerData(mContext); 700 delay(); 701 } 702 } 703 704 // ordered by contact affinity: A, B, C 705 protected class EmailOrderTest extends InteractiveTestCase { 706 @Override 707 View inflate(ViewGroup parent) { 708 return createAutoItem(parent, R.string.attention_email_order); 709 } 710 711 @Override 712 void setUp() { 713 sendNotifications(MODE_EMAIL, false, false); 714 status = READY; 715 // wait for notifications to move through the system 716 delay(); 717 } 718 719 @Override 720 void test() { 721 MockListener.probeListenerOrder(mContext, 722 new MockListener.StringListResultCatcher() { 723 @Override 724 public void accept(List<String> orderedKeys) { 725 int rankA = findTagInKeys(ALICE, orderedKeys); 726 int rankB = findTagInKeys(BOB, orderedKeys); 727 int rankC = findTagInKeys(CHARLIE, orderedKeys); 728 if (rankA < rankB && rankB < rankC) { 729 status = PASS; 730 } else { 731 logFail(rankA + ", " + rankB + ", " + rankC); 732 status = FAIL; 733 } 734 next(); 735 } 736 }); 737 delay(); // in case the catcher never returns 738 } 739 740 @Override 741 void tearDown() { 742 mNm.cancelAll(); 743 MockListener.resetListenerData(mContext); 744 delay(); 745 } 746 } 747 748 // ordered by contact affinity: A, B, C 749 protected class PhoneOrderTest extends InteractiveTestCase { 750 @Override 751 View inflate(ViewGroup parent) { 752 return createAutoItem(parent, R.string.attention_phone_order); 753 } 754 755 @Override 756 void setUp() { 757 sendNotifications(MODE_PHONE, false, false); 758 status = READY; 759 // wait for notifications to move through the system 760 delay(); 761 } 762 763 @Override 764 void test() { 765 MockListener.probeListenerOrder(mContext, 766 new MockListener.StringListResultCatcher() { 767 @Override 768 public void accept(List<String> orderedKeys) { 769 int rankA = findTagInKeys(ALICE, orderedKeys); 770 int rankB = findTagInKeys(BOB, orderedKeys); 771 int rankC = findTagInKeys(CHARLIE, orderedKeys); 772 if (rankA < rankB && rankB < rankC) { 773 status = PASS; 774 } else { 775 logFail(rankA + ", " + rankB + ", " + rankC); 776 status = FAIL; 777 } 778 next(); 779 } 780 }); 781 delay(); // in case the catcher never returns 782 } 783 784 @Override 785 void tearDown() { 786 mNm.cancelAll(); 787 MockListener.resetListenerData(mContext); 788 delay(); 789 } 790 } 791 792 // Utilities 793 794 // usePriorities true: B, C, A 795 // usePriorities false: 796 // MODE_NONE: C, B, A 797 // otherwise: A, B ,C 798 private void sendNotifications(int annotationMode, boolean usePriorities, boolean noisy) { 799 // TODO(cwren) Fixes flakey tests due to bug 17644321. Remove this line when it is fixed. 800 int baseId = NOTIFICATION_ID + (noisy ? 3 : 0); 801 802 // C, B, A when sorted by time. Times must be in the past. 803 long whenA = System.currentTimeMillis() - 4000000L; 804 long whenB = System.currentTimeMillis() - 2000000L; 805 long whenC = System.currentTimeMillis() - 1000000L; 806 807 // B, C, A when sorted by priorities 808 int priorityA = usePriorities ? Notification.PRIORITY_MIN : Notification.PRIORITY_DEFAULT; 809 int priorityB = usePriorities ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT; 810 int priorityC = usePriorities ? Notification.PRIORITY_LOW : Notification.PRIORITY_DEFAULT; 811 812 Notification.Builder alice = new Notification.Builder(mContext) 813 .setContentTitle(ALICE) 814 .setContentText(ALICE) 815 .setSmallIcon(R.drawable.ic_stat_alice) 816 .setPriority(priorityA) 817 .setCategory(Notification.CATEGORY_MESSAGE) 818 .setWhen(whenA); 819 alice.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0); 820 addPerson(annotationMode, alice, mAliceUri, ALICE_PHONE, ALICE_EMAIL); 821 mNm.notify(ALICE, baseId + 1, alice.build()); 822 823 Notification.Builder bob = new Notification.Builder(mContext) 824 .setContentTitle(BOB) 825 .setContentText(BOB) 826 .setSmallIcon(R.drawable.ic_stat_bob) 827 .setPriority(priorityB) 828 .setCategory(Notification.CATEGORY_MESSAGE) 829 .setWhen(whenB); 830 addPerson(annotationMode, bob, mBobUri, BOB_PHONE, BOB_EMAIL); 831 mNm.notify(BOB, baseId + 2, bob.build()); 832 833 Notification.Builder charlie = new Notification.Builder(mContext) 834 .setContentTitle(CHARLIE) 835 .setContentText(CHARLIE) 836 .setSmallIcon(R.drawable.ic_stat_charlie) 837 .setPriority(priorityC) 838 .setCategory(Notification.CATEGORY_MESSAGE) 839 .setWhen(whenC); 840 addPerson(annotationMode, charlie, mCharlieUri, CHARLIE_PHONE, CHARLIE_EMAIL); 841 mNm.notify(CHARLIE, baseId + 3, charlie.build()); 842 } 843 844 private void addPerson(int mode, Notification.Builder note, 845 Uri uri, String phone, String email) { 846 if (mode == MODE_URI && uri != null) { 847 note.addPerson(uri.toString()); 848 } else if (mode == MODE_PHONE) { 849 note.addPerson(Uri.fromParts("tel", phone, null).toString()); 850 } else if (mode == MODE_EMAIL) { 851 note.addPerson(Uri.fromParts("mailto", email, null).toString()); 852 } 853 } 854 855 private void insertSingleContact(String name, String phone, String email, boolean starred) { 856 final ArrayList<ContentProviderOperation> operationList = 857 new ArrayList<ContentProviderOperation>(); 858 ContentProviderOperation.Builder builder = 859 ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI); 860 builder.withValue(ContactsContract.RawContacts.STARRED, starred ? 1 : 0); 861 operationList.add(builder.build()); 862 863 builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI); 864 builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0); 865 builder.withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); 866 builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name); 867 operationList.add(builder.build()); 868 869 if (phone != null) { 870 builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI); 871 builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0); 872 builder.withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 873 builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE); 874 builder.withValue(Phone.NUMBER, phone); 875 builder.withValue(ContactsContract.Data.IS_PRIMARY, 1); 876 operationList.add(builder.build()); 877 } 878 if (email != null) { 879 builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI); 880 builder.withValueBackReference(Email.RAW_CONTACT_ID, 0); 881 builder.withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 882 builder.withValue(Email.TYPE, Email.TYPE_HOME); 883 builder.withValue(Email.DATA, email); 884 operationList.add(builder.build()); 885 } 886 887 try { 888 mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList); 889 } catch (RemoteException e) { 890 Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); 891 } catch (OperationApplicationException e) { 892 Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); 893 } 894 } 895 896 private Uri lookupContact(String phone) { 897 Cursor c = null; 898 try { 899 Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, 900 Uri.encode(phone)); 901 String[] projection = new String[] { ContactsContract.Contacts._ID, 902 ContactsContract.Contacts.LOOKUP_KEY }; 903 c = mContext.getContentResolver().query(phoneUri, projection, null, null, null); 904 if (c != null && c.getCount() > 0) { 905 c.moveToFirst(); 906 int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY); 907 int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID); 908 String lookupKey = c.getString(lookupIdx); 909 long contactId = c.getLong(idIdx); 910 return ContactsContract.Contacts.getLookupUri(contactId, lookupKey); 911 } 912 } catch (Throwable t) { 913 Log.w(TAG, "Problem getting content resolver or performing contacts query.", t); 914 } finally { 915 if (c != null) { 916 c.close(); 917 } 918 } 919 return null; 920 } 921 922 /** Search a list of notification keys for a givcen tag. */ 923 private int findTagInKeys(String tag, List<String> orderedKeys) { 924 for (int i = 0; i < orderedKeys.size(); i++) { 925 if (orderedKeys.get(i).contains(tag)) { 926 return i; 927 } 928 } 929 return -1; 930 } 931 } 932