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