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