Home | History | Annotate | Download | only in notifications
      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