RankingHelperTest.java revision 41103f42126acbd602de81ac87a2e605492ff528
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 */
16package com.android.server.notification;
17
18import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
19import static android.app.NotificationManager.IMPORTANCE_HIGH;
20import static android.app.NotificationManager.IMPORTANCE_LOW;
21
22import static junit.framework.Assert.assertNull;
23import static junit.framework.Assert.fail;
24
25import org.json.JSONArray;
26import org.json.JSONObject;
27import org.junit.Before;
28import org.junit.Test;
29import org.junit.runner.RunWith;
30
31import com.android.internal.util.FastXmlSerializer;
32
33import org.mockito.Mock;
34import org.mockito.MockitoAnnotations;
35import org.xmlpull.v1.XmlPullParser;
36import org.xmlpull.v1.XmlSerializer;
37
38import android.app.Notification;
39import android.app.NotificationChannelGroup;
40import android.content.Context;
41import android.app.NotificationChannel;
42import android.app.NotificationManager;
43import android.content.pm.ApplicationInfo;
44import android.content.pm.PackageManager;
45import android.graphics.Color;
46import android.media.AudioAttributes;
47import android.net.Uri;
48import android.os.Build;
49import android.os.UserHandle;
50import android.service.notification.StatusBarNotification;
51import android.support.test.InstrumentationRegistry;
52import android.support.test.runner.AndroidJUnit4;
53import android.test.suitebuilder.annotation.SmallTest;
54import android.util.ArrayMap;
55import android.util.Xml;
56
57import java.io.BufferedInputStream;
58import java.io.BufferedOutputStream;
59import java.io.ByteArrayInputStream;
60import java.io.ByteArrayOutputStream;
61import java.util.ArrayList;
62import java.util.Arrays;
63import java.util.HashMap;
64import java.util.List;
65import java.util.Map;
66import java.util.Objects;
67import java.util.concurrent.ThreadLocalRandom;
68
69import static org.junit.Assert.assertEquals;
70import static org.junit.Assert.assertFalse;
71import static org.junit.Assert.assertNotNull;
72import static org.junit.Assert.assertTrue;
73import static org.mockito.Matchers.anyInt;
74import static org.mockito.Matchers.anyString;
75import static org.mockito.Matchers.eq;
76import static org.mockito.Mockito.when;
77
78@SmallTest
79@RunWith(AndroidJUnit4.class)
80public class RankingHelperTest {
81    @Mock
82    NotificationUsageStats mUsageStats;
83    @Mock
84    RankingHandler handler;
85    @Mock
86    PackageManager mPm;
87
88    private Notification mNotiGroupGSortA;
89    private Notification mNotiGroupGSortB;
90    private Notification mNotiNoGroup;
91    private Notification mNotiNoGroup2;
92    private Notification mNotiNoGroupSortA;
93    private NotificationRecord mRecordGroupGSortA;
94    private NotificationRecord mRecordGroupGSortB;
95    private NotificationRecord mRecordNoGroup;
96    private NotificationRecord mRecordNoGroup2;
97    private NotificationRecord mRecordNoGroupSortA;
98    private RankingHelper mHelper;
99    private final String pkg = "com.android.server.notification";
100    private final int uid = 0;
101    private final String pkg2 = "pkg2";
102    private final int uid2 = 1111111;
103    private static final String TEST_CHANNEL_ID = "test_channel_id";
104    private AudioAttributes mAudioAttributes;
105
106    private Context getContext() {
107        return InstrumentationRegistry.getTargetContext();
108    }
109
110    @Before
111    public void setUp() {
112        MockitoAnnotations.initMocks(this);
113        UserHandle user = UserHandle.ALL;
114
115        mHelper = new RankingHelper(getContext(), mPm, handler, mUsageStats,
116                new String[]{ImportanceExtractor.class.getName()});
117
118        mNotiGroupGSortA = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
119                .setContentTitle("A")
120                .setGroup("G")
121                .setSortKey("A")
122                .setWhen(1205)
123                .build();
124        mRecordGroupGSortA = new NotificationRecord(getContext(), new StatusBarNotification(
125                "package", "package", 1, null, 0, 0, mNotiGroupGSortA, user,
126                null, System.currentTimeMillis()), getDefaultChannel());
127
128        mNotiGroupGSortB = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
129                .setContentTitle("B")
130                .setGroup("G")
131                .setSortKey("B")
132                .setWhen(1200)
133                .build();
134        mRecordGroupGSortB = new NotificationRecord(getContext(), new StatusBarNotification(
135                "package", "package", 1, null, 0, 0, mNotiGroupGSortB, user,
136                null, System.currentTimeMillis()), getDefaultChannel());
137
138        mNotiNoGroup = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
139                .setContentTitle("C")
140                .setWhen(1201)
141                .build();
142        mRecordNoGroup = new NotificationRecord(getContext(), new StatusBarNotification(
143                "package", "package", 1, null, 0, 0, mNotiNoGroup, user,
144                null, System.currentTimeMillis()), getDefaultChannel());
145
146        mNotiNoGroup2 = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
147                .setContentTitle("D")
148                .setWhen(1202)
149                .build();
150        mRecordNoGroup2 = new NotificationRecord(getContext(), new StatusBarNotification(
151                "package", "package", 1, null, 0, 0, mNotiNoGroup2, user,
152                null, System.currentTimeMillis()), getDefaultChannel());
153
154        mNotiNoGroupSortA = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
155                .setContentTitle("E")
156                .setWhen(1201)
157                .setSortKey("A")
158                .build();
159        mRecordNoGroupSortA = new NotificationRecord(getContext(), new StatusBarNotification(
160                "package", "package", 1, null, 0, 0, mNotiNoGroupSortA, user,
161                null, System.currentTimeMillis()), getDefaultChannel());
162
163        mAudioAttributes = new AudioAttributes.Builder()
164                .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
165                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
166                .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
167                .build();
168
169        final ApplicationInfo legacy = new ApplicationInfo();
170        legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
171        final ApplicationInfo upgrade = new ApplicationInfo();
172        upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
173        try {
174            when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(legacy);
175            when(mPm.getApplicationInfoAsUser(eq(pkg2), anyInt(), anyInt())).thenReturn(upgrade);
176        } catch (PackageManager.NameNotFoundException e) {
177        }
178    }
179
180    private NotificationChannel getDefaultChannel() {
181        return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
182                IMPORTANCE_LOW);
183    }
184
185    private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, String... channelIds)
186            throws Exception {
187        XmlSerializer serializer = new FastXmlSerializer();
188        ByteArrayOutputStream baos = new ByteArrayOutputStream();
189        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
190        serializer.startDocument(null, true);
191        serializer.startTag(null, "ranking");
192        mHelper.writeXml(serializer, false);
193        serializer.endTag(null, "ranking");
194        serializer.endDocument();
195        serializer.flush();
196
197        for (String channelId : channelIds) {
198            mHelper.permanentlyDeleteNotificationChannel(pkg, uid, channelId);
199        }
200        return baos;
201    }
202
203    private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
204        assertEquals(expected.getId(), actual.getId());
205        assertEquals(expected.getName(), actual.getName());
206        assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
207        assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
208        assertEquals(expected.getImportance(), actual.getImportance());
209        assertEquals(expected.getLockscreenVisibility(), actual.getLockscreenVisibility());
210        assertEquals(expected.getSound(), actual.getSound());
211        assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
212        assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
213        assertEquals(expected.getGroup(), actual.getGroup());
214        assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
215        assertEquals(expected.getLightColor(), actual.getLightColor());
216    }
217
218    private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
219        assertEquals(expected.getId(), actual.getId());
220        assertEquals(expected.getName(), actual.getName());
221    }
222
223    @Test
224    public void testFindAfterRankingWithASplitGroup() throws Exception {
225        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
226        notificationList.add(mRecordGroupGSortA);
227        notificationList.add(mRecordGroupGSortB);
228        notificationList.add(mRecordNoGroup);
229        notificationList.add(mRecordNoGroupSortA);
230        mHelper.sort(notificationList);
231        assertTrue(mHelper.indexOf(notificationList, mRecordGroupGSortA) >= 0);
232        assertTrue(mHelper.indexOf(notificationList, mRecordGroupGSortB) >= 0);
233        assertTrue(mHelper.indexOf(notificationList, mRecordNoGroup) >= 0);
234        assertTrue(mHelper.indexOf(notificationList, mRecordNoGroupSortA) >= 0);
235    }
236
237    @Test
238    public void testSortShouldNotThrowWithPlainNotifications() throws Exception {
239        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(2);
240        notificationList.add(mRecordNoGroup);
241        notificationList.add(mRecordNoGroup2);
242        mHelper.sort(notificationList);
243    }
244
245    @Test
246    public void testSortShouldNotThrowOneSorted() throws Exception {
247        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(2);
248        notificationList.add(mRecordNoGroup);
249        notificationList.add(mRecordNoGroupSortA);
250        mHelper.sort(notificationList);
251    }
252
253    @Test
254    public void testSortShouldNotThrowOneNotification() throws Exception {
255        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(1);
256        notificationList.add(mRecordNoGroup);
257        mHelper.sort(notificationList);
258    }
259
260    @Test
261    public void testSortShouldNotThrowOneSortKey() throws Exception {
262        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(1);
263        notificationList.add(mRecordGroupGSortB);
264        mHelper.sort(notificationList);
265    }
266
267    @Test
268    public void testSortShouldNotThrowOnEmptyList() throws Exception {
269        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>();
270        mHelper.sort(notificationList);
271    }
272
273    @Test
274    public void testChannelXml() throws Exception {
275        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
276        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
277        NotificationChannel channel1 =
278                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
279        NotificationChannel channel2 =
280                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
281        channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
282        channel2.enableLights(true);
283        channel2.setBypassDnd(true);
284        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
285        channel2.enableVibration(true);
286        channel2.setGroup(ncg.getId());
287        channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
288        channel2.setLightColor(Color.BLUE);
289
290        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
291        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
292        mHelper.createNotificationChannel(pkg, uid, channel1, true);
293        mHelper.createNotificationChannel(pkg, uid, channel2, false);
294
295        mHelper.setShowBadge(pkg, uid, true);
296        mHelper.setShowBadge(pkg2, uid2, false);
297
298        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(), channel2.getId(),
299                NotificationChannel.DEFAULT_CHANNEL_ID);
300        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
301
302        XmlPullParser parser = Xml.newPullParser();
303        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
304                null);
305        parser.nextTag();
306        mHelper.readXml(parser, false);
307
308        assertFalse(mHelper.canShowBadge(pkg2, uid2));
309        assertTrue(mHelper.canShowBadge(pkg, uid));
310        assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
311        compareChannels(channel2,
312                mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
313        assertNotNull(mHelper.getNotificationChannel(
314                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false));
315
316        List<NotificationChannelGroup> actualGroups =
317                mHelper.getNotificationChannelGroups(pkg, uid, false).getList();
318        boolean foundNcg = false;
319        for (NotificationChannelGroup actual : actualGroups) {
320            if (ncg.getId().equals(actual.getId())) {
321                foundNcg = true;
322                compareGroups(ncg, actual);
323            } else if (ncg2.getId().equals(actual.getId())) {
324                compareGroups(ncg2, actual);
325            }
326        }
327        assertTrue(foundNcg);
328
329        boolean foundChannel2Group = false;
330        for (NotificationChannelGroup actual : actualGroups) {
331            if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
332                foundChannel2Group = true;
333                break;
334            }
335        }
336        assertTrue(foundChannel2Group);
337    }
338
339    @Test
340    public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
341        NotificationChannel channel1 =
342                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
343
344        mHelper.createNotificationChannel(pkg, uid, channel1, true);
345
346        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(),
347                NotificationChannel.DEFAULT_CHANNEL_ID);
348
349        XmlPullParser parser = Xml.newPullParser();
350        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
351                null);
352        parser.nextTag();
353        mHelper.readXml(parser, false);
354
355        final NotificationChannel updated = mHelper.getNotificationChannel(
356                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
357        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
358        assertFalse(updated.canBypassDnd());
359        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
360        assertEquals(0, updated.getUserLockedFields());
361    }
362
363    @Test
364    public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
365        NotificationChannel channel1 =
366                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MIN);
367        mHelper.createNotificationChannel(pkg, uid, channel1, true);
368
369        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
370                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
371        defaultChannel.setImportance(IMPORTANCE_LOW);
372        mHelper.updateNotificationChannel(pkg, uid, defaultChannel);
373
374        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(),
375                NotificationChannel.DEFAULT_CHANNEL_ID);
376
377        XmlPullParser parser = Xml.newPullParser();
378        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
379                null);
380        parser.nextTag();
381        mHelper.readXml(parser, false);
382
383        assertEquals(IMPORTANCE_LOW, mHelper.getNotificationChannel(
384                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
385    }
386
387    @Test
388    public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
389        final String preupgradeXml = "<ranking version=\"1\">\n"
390                + "<package name=\"" + pkg + "\" importance=\""
391                + NotificationManager.IMPORTANCE_HIGH
392                + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
393                + Notification.VISIBILITY_SECRET + "\"" + " uid=\"" + uid + "\" />\n"
394                + "<package name=\"" + pkg2 + "\" uid=\"" + uid2 + "\" visibility=\""
395                + Notification.VISIBILITY_PRIVATE + "\" />\n"
396                + "</ranking>";
397        XmlPullParser parser = Xml.newPullParser();
398        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
399                null);
400        parser.nextTag();
401        mHelper.readXml(parser, false);
402
403        final NotificationChannel updated1 = mHelper.getNotificationChannel(
404                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
405        assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
406        assertTrue(updated1.canBypassDnd());
407        assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
408        assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
409                | NotificationChannel.USER_LOCKED_PRIORITY
410                | NotificationChannel.USER_LOCKED_VISIBILITY, updated1.getUserLockedFields());
411
412        final NotificationChannel updated2 = mHelper.getNotificationChannel(
413                pkg2, uid2, NotificationChannel.DEFAULT_CHANNEL_ID, false);
414        // clamped
415        assertEquals(IMPORTANCE_LOW, updated2.getImportance());
416        assertFalse(updated2.canBypassDnd());
417        assertEquals(Notification.VISIBILITY_PRIVATE, updated2.getLockscreenVisibility());
418        assertEquals(NotificationChannel.USER_LOCKED_VISIBILITY, updated2.getUserLockedFields());
419    }
420
421    @Test
422    public void testCreateChannel_blocked() throws Exception {
423        mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_NONE);
424
425        mHelper.createNotificationChannel(pkg, uid,
426                new NotificationChannel(pkg, "bananas", IMPORTANCE_LOW), true);
427    }
428
429    @Test
430    public void testUpdate_userLockedImportance() throws Exception {
431        // all fields locked by user
432        final NotificationChannel channel =
433                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
434        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
435
436        mHelper.createNotificationChannel(pkg, uid, channel, false);
437
438        // same id, try to update
439        final NotificationChannel channel2 =
440                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
441
442        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
443
444        // no fields should be changed
445        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
446    }
447
448    @Test
449    public void testUpdate_userLockedVisibility() throws Exception {
450        // all fields locked by user
451        final NotificationChannel channel =
452                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
453        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
454        channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
455
456        mHelper.createNotificationChannel(pkg, uid, channel, false);
457
458        // same id, try to update
459        final NotificationChannel channel2 =
460                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
461        channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
462
463        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
464
465        // no fields should be changed
466        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
467    }
468
469    @Test
470    public void testUpdate_userLockedVibration() throws Exception {
471        // all fields locked by user
472        final NotificationChannel channel =
473                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
474        channel.enableLights(false);
475        channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
476
477        mHelper.createNotificationChannel(pkg, uid, channel, false);
478
479        // same id, try to update
480        final NotificationChannel channel2 =
481                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
482        channel2.enableVibration(true);
483        channel2.setVibrationPattern(new long[]{100});
484
485        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
486
487        // no fields should be changed
488        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
489    }
490
491    @Test
492    public void testUpdate_userLockedLights() throws Exception {
493        // all fields locked by user
494        final NotificationChannel channel =
495                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
496        channel.enableLights(false);
497        channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
498
499        mHelper.createNotificationChannel(pkg, uid, channel, false);
500
501        // same id, try to update
502        final NotificationChannel channel2 =
503                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
504        channel2.enableLights(true);
505
506        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
507
508        // no fields should be changed
509        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
510    }
511
512    @Test
513    public void testUpdate_userLockedPriority() throws Exception {
514        // all fields locked by user
515        final NotificationChannel channel =
516                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
517        channel.setBypassDnd(true);
518        channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
519
520        mHelper.createNotificationChannel(pkg, uid, channel, false);
521
522        // same id, try to update all fields
523        final NotificationChannel channel2 =
524                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
525        channel2.setBypassDnd(false);
526
527        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
528
529        // no fields should be changed
530        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
531    }
532
533    @Test
534    public void testUpdate_userLockedRingtone() throws Exception {
535        // all fields locked by user
536        final NotificationChannel channel =
537                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
538        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
539        channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
540
541        mHelper.createNotificationChannel(pkg, uid, channel, false);
542
543        // same id, try to update all fields
544        final NotificationChannel channel2 =
545                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
546        channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
547
548        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
549
550        // no fields should be changed
551        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
552    }
553
554    @Test
555    public void testUpdate_userLockedBadge() throws Exception {
556        final NotificationChannel channel =
557                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
558        channel.setShowBadge(true);
559        channel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
560
561        mHelper.createNotificationChannel(pkg, uid, channel, false);
562
563        final NotificationChannel channel2 =
564                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
565        channel2.setShowBadge(false);
566
567        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
568
569        // no fields should be changed
570        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
571    }
572
573    @Test
574    public void testUpdate() throws Exception {
575        // no fields locked by user
576        final NotificationChannel channel =
577                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
578        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
579        channel.enableLights(true);
580        channel.setBypassDnd(true);
581        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
582
583        mHelper.createNotificationChannel(pkg, uid, channel, false);
584
585        // same id, try to update all fields
586        final NotificationChannel channel2 =
587                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
588        channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
589        channel2.enableLights(false);
590        channel2.setBypassDnd(false);
591        channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
592
593        mHelper.updateNotificationChannel(pkg, uid, channel2);
594
595        // all fields should be changed
596        assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
597    }
598
599    @Test
600    public void testGetChannelWithFallback() throws Exception {
601        NotificationChannel channel =
602                mHelper.getNotificationChannelWithFallback(pkg, uid, "garbage", false);
603        assertEquals(NotificationChannel.DEFAULT_CHANNEL_ID, channel.getId());
604    }
605
606    @Test
607    public void testCreateChannel_CannotChangeHiddenFields() throws Exception {
608        final NotificationChannel channel =
609                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
610        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
611        channel.enableLights(true);
612        channel.setBypassDnd(true);
613        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
614        channel.setShowBadge(true);
615        int lockMask = 0;
616        for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
617            lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
618        }
619        channel.lockFields(lockMask);
620
621        mHelper.createNotificationChannel(pkg, uid, channel, true);
622
623        NotificationChannel savedChannel =
624                mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
625
626        assertEquals(channel.getName(), savedChannel.getName());
627        assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
628        assertFalse(savedChannel.canBypassDnd());
629        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
630        assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
631    }
632
633    @Test
634    public void testCreateChannel_CannotChangeHiddenFieldsAssistant() throws Exception {
635        final NotificationChannel channel =
636                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
637        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
638        channel.enableLights(true);
639        channel.setBypassDnd(true);
640        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
641        channel.setShowBadge(true);
642        int lockMask = 0;
643        for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
644            lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
645        }
646        channel.lockFields(lockMask);
647
648        mHelper.createNotificationChannel(pkg, uid, channel, true);
649
650        NotificationChannel savedChannel =
651                mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
652
653        assertEquals(channel.getName(), savedChannel.getName());
654        assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
655        assertFalse(savedChannel.canBypassDnd());
656        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
657        assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
658    }
659
660    @Test
661    public void testGetDeletedChannel() throws Exception {
662        NotificationChannel channel =
663                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
664        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
665        channel.enableLights(true);
666        channel.setBypassDnd(true);
667        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
668        channel.enableVibration(true);
669        channel.setVibrationPattern(new long[]{100, 67, 145, 156});
670
671        mHelper.createNotificationChannel(pkg, uid, channel, true);
672        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
673
674        // Does not return deleted channel
675        NotificationChannel response =
676                mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
677        assertNull(response);
678
679        // Returns deleted channel
680        response = mHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
681        compareChannels(channel, response);
682        assertTrue(response.isDeleted());
683    }
684
685    @Test
686    public void testGetDeletedChannels() throws Exception {
687        Map<String, NotificationChannel> channelMap = new HashMap<>();
688        NotificationChannel channel =
689                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
690        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
691        channel.enableLights(true);
692        channel.setBypassDnd(true);
693        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
694        channel.enableVibration(true);
695        channel.setVibrationPattern(new long[]{100, 67, 145, 156});
696        channelMap.put(channel.getId(), channel);
697        NotificationChannel channel2 =
698                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
699        channelMap.put(channel2.getId(), channel2);
700        mHelper.createNotificationChannel(pkg, uid, channel, true);
701        mHelper.createNotificationChannel(pkg, uid, channel2, true);
702
703        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
704
705        // Returns only non-deleted channels
706        List<NotificationChannel> channels =
707                mHelper.getNotificationChannels(pkg, uid, false).getList();
708        assertEquals(2, channels.size());   // Default channel + non-deleted channel
709        for (NotificationChannel nc : channels) {
710            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
711                compareChannels(channel2, nc);
712            }
713        }
714
715        // Returns deleted channels too
716        channels = mHelper.getNotificationChannels(pkg, uid, true).getList();
717        assertEquals(3, channels.size());               // Includes default channel
718        for (NotificationChannel nc : channels) {
719            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
720                compareChannels(channelMap.get(nc.getId()), nc);
721            }
722        }
723    }
724
725    @Test
726    public void testGetDeletedChannelCount() throws Exception {
727        NotificationChannel channel =
728                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
729        NotificationChannel channel2 =
730                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
731        NotificationChannel channel3 =
732                new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
733        mHelper.createNotificationChannel(pkg, uid, channel, true);
734        mHelper.createNotificationChannel(pkg, uid, channel2, true);
735        mHelper.createNotificationChannel(pkg, uid, channel3, true);
736
737        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
738        mHelper.deleteNotificationChannel(pkg, uid, channel3.getId());
739
740        assertEquals(2, mHelper.getDeletedChannelCount(pkg, uid));
741        assertEquals(0, mHelper.getDeletedChannelCount(pkg2, uid2));
742    }
743
744    @Test
745    public void testUpdateDeletedChannels() throws Exception {
746        NotificationChannel channel =
747                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
748        mHelper.createNotificationChannel(pkg, uid, channel, true);
749
750        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
751
752        channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
753        try {
754            mHelper.updateNotificationChannel(pkg, uid, channel);
755            fail("Updated deleted channel");
756        } catch (IllegalArgumentException e) {
757            // :)
758        }
759
760        try {
761            mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
762            fail("Updated deleted channel");
763        } catch (IllegalArgumentException e) {
764            // :)
765        }
766    }
767
768    @Test
769    public void testCreateDeletedChannel() throws Exception {
770        long[] vibration = new long[]{100, 67, 145, 156};
771        NotificationChannel channel =
772                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
773        channel.setVibrationPattern(vibration);
774
775        mHelper.createNotificationChannel(pkg, uid, channel, true);
776        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
777
778        NotificationChannel newChannel = new NotificationChannel(
779                channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
780        newChannel.setVibrationPattern(new long[]{100});
781
782        mHelper.createNotificationChannel(pkg, uid, newChannel, true);
783
784        // No long deleted, using old settings
785        compareChannels(channel,
786                mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
787    }
788
789    @Test
790    public void testCreateChannel_defaultChannelId() throws Exception {
791        try {
792            mHelper.createNotificationChannel(pkg2, uid2, new NotificationChannel(
793                    NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true);
794            fail("Allowed to create default channel");
795        } catch (IllegalArgumentException e) {
796            // pass
797        }
798    }
799
800    @Test
801    public void testCreateChannel_alreadyExists() throws Exception {
802        long[] vibration = new long[]{100, 67, 145, 156};
803        NotificationChannel channel =
804                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
805        channel.setVibrationPattern(vibration);
806
807        mHelper.createNotificationChannel(pkg, uid, channel, true);
808
809        NotificationChannel newChannel = new NotificationChannel(
810                channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
811        newChannel.setVibrationPattern(new long[]{100});
812
813        mHelper.createNotificationChannel(pkg, uid, newChannel, true);
814
815        // Old settings not overridden
816        compareChannels(channel,
817                mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
818    }
819
820    @Test
821    public void testCreateChannel_addMissingSound() throws Exception {
822        final NotificationChannel channel =
823                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
824        mHelper.createNotificationChannel(pkg, uid, channel, true);
825        assertNotNull(mHelper.getNotificationChannel(
826                pkg, uid, channel.getId(), false).getSound());
827    }
828
829    @Test
830    public void testCreateChannel_noOverrideSound() throws Exception {
831        Uri sound = new Uri.Builder().scheme("test").build();
832        final NotificationChannel channel = new NotificationChannel("id2", "name2",
833                 NotificationManager.IMPORTANCE_DEFAULT);
834        channel.setSound(sound, mAudioAttributes);
835        mHelper.createNotificationChannel(pkg, uid, channel, true);
836        assertEquals(sound, mHelper.getNotificationChannel(
837                pkg, uid, channel.getId(), false).getSound());
838    }
839
840    @Test
841    public void testPermanentlyDeleteChannels() throws Exception {
842        NotificationChannel channel1 =
843                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
844        NotificationChannel channel2 =
845                new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
846
847        mHelper.createNotificationChannel(pkg, uid, channel1, true);
848        mHelper.createNotificationChannel(pkg, uid, channel2, false);
849
850        mHelper.permanentlyDeleteNotificationChannels(pkg, uid);
851
852        // Only default channel remains
853        assertEquals(1, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
854    }
855
856    @Test
857    public void testOnPackageChanged_packageRemoval() throws Exception {
858        // Deleted
859        NotificationChannel channel1 =
860                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
861        mHelper.createNotificationChannel(pkg, uid, channel1, true);
862
863        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
864
865        assertEquals(0, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
866
867        // Not deleted
868        mHelper.createNotificationChannel(pkg, uid, channel1, true);
869
870        mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
871        assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
872    }
873
874    @Test
875    public void testOnPackageChanged_packageRemoval_importance() throws Exception {
876        mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_HIGH);
877
878        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
879
880        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
881    }
882
883    @Test
884    public void testOnPackageChanged_packageRemoval_groups() throws Exception {
885        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
886        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
887        NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
888        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
889
890        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
891
892        assertEquals(0, mHelper.getNotificationChannelGroups(pkg, uid, true).getList().size());
893    }
894
895    @Test
896    public void testRecordDefaults() throws Exception {
897        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
898        assertEquals(true, mHelper.canShowBadge(pkg, uid));
899        assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
900    }
901
902    @Test
903    public void testCreateGroup() throws Exception {
904        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
905        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
906        assertEquals(ncg, mHelper.getNotificationChannelGroups(pkg, uid).iterator().next());
907    }
908
909    @Test
910    public void testCannotCreateChannel_badGroup() throws Exception {
911        NotificationChannel channel1 =
912                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
913        channel1.setGroup("garbage");
914        try {
915            mHelper.createNotificationChannel(pkg, uid, channel1, true);
916            fail("Created a channel with a bad group");
917        } catch (IllegalArgumentException e) {
918        }
919    }
920
921    @Test
922    public void testCannotCreateChannel_goodGroup() throws Exception {
923        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
924        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
925        NotificationChannel channel1 =
926                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
927        channel1.setGroup(ncg.getId());
928        mHelper.createNotificationChannel(pkg, uid, channel1, true);
929
930        assertEquals(ncg.getId(),
931                mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false).getGroup());
932    }
933
934    @Test
935    public void testGetChannelGroups() throws Exception {
936        NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
937        mHelper.createNotificationChannelGroup(pkg, uid, unused, true);
938        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
939        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
940        NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
941        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
942
943        NotificationChannel channel1 =
944                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
945        channel1.setGroup(ncg.getId());
946        mHelper.createNotificationChannel(pkg, uid, channel1, true);
947        NotificationChannel channel1a =
948                new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
949        channel1a.setGroup(ncg.getId());
950        mHelper.createNotificationChannel(pkg, uid, channel1a, true);
951
952        NotificationChannel channel2 =
953                new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
954        channel2.setGroup(ncg2.getId());
955        mHelper.createNotificationChannel(pkg, uid, channel2, true);
956
957        NotificationChannel channel3 =
958                new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
959        mHelper.createNotificationChannel(pkg, uid, channel3, true);
960
961        List<NotificationChannelGroup> actual =
962                mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
963        assertEquals(3, actual.size());
964        for (NotificationChannelGroup group : actual) {
965            if (group.getId() == null) {
966                assertEquals(2, group.getChannels().size()); // misc channel too
967                assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
968                        || channel3.getId().equals(group.getChannels().get(1).getId()));
969            } else if (group.getId().equals(ncg.getId())) {
970                assertEquals(2, group.getChannels().size());
971                if (group.getChannels().get(0).getId().equals(channel1.getId())) {
972                    assertTrue(group.getChannels().get(1).getId().equals(channel1a.getId()));
973                } else if (group.getChannels().get(0).getId().equals(channel1a.getId())) {
974                    assertTrue(group.getChannels().get(1).getId().equals(channel1.getId()));
975                } else {
976                    fail("expected channel not found");
977                }
978            } else if (group.getId().equals(ncg2.getId())) {
979                assertEquals(1, group.getChannels().size());
980                assertEquals(channel2.getId(), group.getChannels().get(0).getId());
981            }
982        }
983    }
984
985    @Test
986    public void testGetChannelGroups_noSideEffects() throws Exception {
987        NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
988        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
989
990        NotificationChannel channel1 =
991                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
992        channel1.setGroup(ncg.getId());
993        mHelper.createNotificationChannel(pkg, uid, channel1, true);
994        mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
995
996        channel1.setImportance(IMPORTANCE_LOW);
997        mHelper.updateNotificationChannel(pkg, uid, channel1);
998
999        List<NotificationChannelGroup> actual =
1000                mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
1001
1002        assertEquals(2, actual.size());
1003        for (NotificationChannelGroup group : actual) {
1004            if (Objects.equals(group.getId(), ncg.getId())) {
1005                assertEquals(1, group.getChannels().size());
1006            }
1007        }
1008    }
1009
1010    @Test
1011    public void testCreateChannel_updateName() throws Exception {
1012        NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
1013        mHelper.createNotificationChannel(pkg, uid, nc, true);
1014        NotificationChannel actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
1015        assertEquals("hello", actual.getName());
1016
1017        nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
1018        mHelper.createNotificationChannel(pkg, uid, nc, true);
1019
1020        actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
1021        assertEquals("goodbye", actual.getName());
1022        assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
1023    }
1024
1025    @Test
1026    public void testDumpChannelsJson() throws Exception {
1027        final ApplicationInfo upgrade = new ApplicationInfo();
1028        upgrade.targetSdkVersion = Build.VERSION_CODES.O;
1029        try {
1030            when(mPm.getApplicationInfoAsUser(
1031                    anyString(), anyInt(), anyInt())).thenReturn(upgrade);
1032        } catch (PackageManager.NameNotFoundException e) {
1033        }
1034        ArrayMap<String, Integer> expectedChannels = new ArrayMap<>();
1035        int numPackages = ThreadLocalRandom.current().nextInt(1, 5);
1036        for (int i = 0; i < numPackages; i++) {
1037            String pkgName = "pkg" + i;
1038            int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
1039            for (int j = 0; j < numChannels; j++) {
1040                mHelper.createNotificationChannel(pkgName, uid,
1041                        new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true);
1042            }
1043            expectedChannels.put(pkgName, numChannels);
1044        }
1045
1046        // delete the first channel of the first package
1047        String pkg = expectedChannels.keyAt(0);
1048        mHelper.deleteNotificationChannel("pkg" + 0, uid, "0");
1049        // dump should not include deleted channels
1050        int count = expectedChannels.get(pkg);
1051        expectedChannels.put(pkg, count - 1);
1052
1053        JSONArray actual = mHelper.dumpChannelsJson(new NotificationManagerService.DumpFilter());
1054        assertEquals(numPackages, actual.length());
1055        for (int i = 0; i < numPackages; i++) {
1056            JSONObject object = actual.getJSONObject(i);
1057            assertTrue(expectedChannels.containsKey(object.get("packageName")));
1058            assertEquals(expectedChannels.get(object.get("packageName")).intValue() + 1,
1059                    object.getInt("channelCount"));
1060        }
1061    }
1062}
1063