RankingHelperTest.java revision b879cc614f58cf0237bdc50393db129afe2b9403
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 org.junit.Before;
19import org.junit.Test;
20import org.junit.runner.RunWith;
21import com.android.internal.util.FastXmlSerializer;
22
23import org.mockito.Mock;
24import org.mockito.MockitoAnnotations;
25import org.xmlpull.v1.XmlPullParser;
26import org.xmlpull.v1.XmlSerializer;
27
28import android.app.Notification;
29import android.content.Context;
30import android.app.NotificationChannel;
31import android.app.NotificationManager;
32import android.content.pm.ApplicationInfo;
33import android.content.pm.PackageManager;
34import android.net.Uri;
35import android.os.Build;
36import android.os.UserHandle;
37import android.service.notification.StatusBarNotification;
38import android.support.test.InstrumentationRegistry;
39import android.support.test.runner.AndroidJUnit4;
40import android.test.suitebuilder.annotation.SmallTest;
41import android.util.Xml;
42
43import java.io.BufferedInputStream;
44import java.io.BufferedOutputStream;
45import java.io.ByteArrayInputStream;
46import java.io.ByteArrayOutputStream;
47import java.util.ArrayList;
48
49import static org.junit.Assert.assertEquals;
50import static org.junit.Assert.assertFalse;
51import static org.junit.Assert.assertNotNull;
52import static org.junit.Assert.assertTrue;
53import static org.mockito.Matchers.anyInt;
54import static org.mockito.Matchers.anyString;
55import static org.mockito.Mockito.when;
56
57@SmallTest
58@RunWith(AndroidJUnit4.class)
59public class RankingHelperTest {
60    @Mock NotificationUsageStats mUsageStats;
61    @Mock RankingHandler handler;
62    @Mock PackageManager mPm;
63
64    private Notification mNotiGroupGSortA;
65    private Notification mNotiGroupGSortB;
66    private Notification mNotiNoGroup;
67    private Notification mNotiNoGroup2;
68    private Notification mNotiNoGroupSortA;
69    private NotificationRecord mRecordGroupGSortA;
70    private NotificationRecord mRecordGroupGSortB;
71    private NotificationRecord mRecordNoGroup;
72    private NotificationRecord mRecordNoGroup2;
73    private NotificationRecord mRecordNoGroupSortA;
74    private RankingHelper mHelper;
75    private final String pkg = "com.android.server.notification";
76    private final int uid = 0;
77
78    private Context getContext() {
79        return InstrumentationRegistry.getTargetContext();
80    }
81
82    @Before
83    public void setUp() {
84        MockitoAnnotations.initMocks(this);
85        UserHandle user = UserHandle.ALL;
86
87        mHelper = new RankingHelper(getContext(), mPm, handler, mUsageStats,
88                new String[] {ImportanceExtractor.class.getName()});
89
90        mNotiGroupGSortA = new Notification.Builder(getContext())
91                .setContentTitle("A")
92                .setGroup("G")
93                .setSortKey("A")
94                .setWhen(1205)
95                .build();
96        mRecordGroupGSortA = new NotificationRecord(getContext(), new StatusBarNotification(
97                "package", "package", 1, null, 0, 0, 0, mNotiGroupGSortA, user),
98                getDefaultChannel());
99
100        mNotiGroupGSortB = new Notification.Builder(getContext())
101                .setContentTitle("B")
102                .setGroup("G")
103                .setSortKey("B")
104                .setWhen(1200)
105                .build();
106        mRecordGroupGSortB = new NotificationRecord(getContext(), new StatusBarNotification(
107                "package", "package", 1, null, 0, 0, 0, mNotiGroupGSortB, user),
108                getDefaultChannel());
109
110        mNotiNoGroup = new Notification.Builder(getContext())
111                .setContentTitle("C")
112                .setWhen(1201)
113                .build();
114        mRecordNoGroup = new NotificationRecord(getContext(), new StatusBarNotification(
115                "package", "package", 1, null, 0, 0, 0, mNotiNoGroup, user),
116                getDefaultChannel());
117
118        mNotiNoGroup2 = new Notification.Builder(getContext())
119                .setContentTitle("D")
120                .setWhen(1202)
121                .build();
122        mRecordNoGroup2 = new NotificationRecord(getContext(), new StatusBarNotification(
123                "package", "package", 1, null, 0, 0, 0, mNotiNoGroup2, user),
124                getDefaultChannel());
125
126        mNotiNoGroupSortA = new Notification.Builder(getContext())
127                .setContentTitle("E")
128                .setWhen(1201)
129                .setSortKey("A")
130                .build();
131        mRecordNoGroupSortA = new NotificationRecord(getContext(), new StatusBarNotification(
132                "package", "package", 1, null, 0, 0, 0, mNotiNoGroupSortA, user),
133                getDefaultChannel());
134
135        final ApplicationInfo legacy = new ApplicationInfo();
136        legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
137        try {
138            when(mPm.getApplicationInfo(anyString(), anyInt())).thenReturn(legacy);
139        } catch (PackageManager.NameNotFoundException e) {}
140    }
141
142    private NotificationChannel getDefaultChannel() {
143        return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
144                NotificationManager.IMPORTANCE_LOW);
145    }
146
147    private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, String... channelIds)
148            throws Exception {
149        XmlSerializer serializer = new FastXmlSerializer();
150        ByteArrayOutputStream baos = new ByteArrayOutputStream();
151        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
152        serializer.startDocument(null, true);
153        serializer.startTag(null, "ranking");
154        mHelper.writeXml(serializer, false);
155        serializer.endTag(null, "ranking");
156        serializer.endDocument();
157        serializer.flush();
158
159        for (String channelId : channelIds) {
160            mHelper.deleteNotificationChannel(pkg, uid, channelId);
161        }
162        return baos;
163    }
164
165    @Test
166    public void testFindAfterRankingWithASplitGroup() throws Exception {
167        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
168        notificationList.add(mRecordGroupGSortA);
169        notificationList.add(mRecordGroupGSortB);
170        notificationList.add(mRecordNoGroup);
171        notificationList.add(mRecordNoGroupSortA);
172        mHelper.sort(notificationList);
173        assertTrue(mHelper.indexOf(notificationList, mRecordGroupGSortA) >= 0);
174        assertTrue(mHelper.indexOf(notificationList, mRecordGroupGSortB) >= 0);
175        assertTrue(mHelper.indexOf(notificationList, mRecordNoGroup) >= 0);
176        assertTrue(mHelper.indexOf(notificationList, mRecordNoGroupSortA) >= 0);
177    }
178
179    @Test
180    public void testSortShouldNotThrowWithPlainNotifications() throws Exception {
181        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(2);
182        notificationList.add(mRecordNoGroup);
183        notificationList.add(mRecordNoGroup2);
184        mHelper.sort(notificationList);
185    }
186
187    @Test
188    public void testSortShouldNotThrowOneSorted() throws Exception {
189        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(2);
190        notificationList.add(mRecordNoGroup);
191        notificationList.add(mRecordNoGroupSortA);
192        mHelper.sort(notificationList);
193    }
194
195    @Test
196    public void testSortShouldNotThrowOneNotification() throws Exception {
197        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(1);
198        notificationList.add(mRecordNoGroup);
199        mHelper.sort(notificationList);
200    }
201
202    @Test
203    public void testSortShouldNotThrowOneSortKey() throws Exception {
204        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(1);
205        notificationList.add(mRecordGroupGSortB);
206        mHelper.sort(notificationList);
207    }
208
209    @Test
210    public void testSortShouldNotThrowOnEmptyList() throws Exception {
211        ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>();
212        mHelper.sort(notificationList);
213    }
214
215    @Test
216    public void testChannelXml() throws Exception {
217        NotificationChannel channel1 =
218                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
219        NotificationChannel channel2 =
220                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
221        channel2.setRingtone(new Uri.Builder().scheme("test").build());
222        channel2.setLights(true);
223        channel2.setBypassDnd(true);
224        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
225
226        mHelper.createNotificationChannel(pkg, uid, channel1);
227        mHelper.createNotificationChannel(pkg, uid, channel2);
228
229        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(), channel2.getId(),
230                NotificationChannel.DEFAULT_CHANNEL_ID);
231
232        mHelper.deleteNotificationChannel(pkg, uid, channel1.getId());
233        mHelper.deleteNotificationChannel(pkg, uid, channel2.getId());
234        mHelper.deleteNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
235
236        XmlPullParser parser = Xml.newPullParser();
237        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
238                null);
239        parser.nextTag();
240        mHelper.readXml(parser, false);
241
242        assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId()));
243        assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId()));
244        assertNotNull(
245                mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID));
246    }
247
248    @Test
249    public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
250        NotificationChannel channel1 =
251                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
252
253        mHelper.createNotificationChannel(pkg, uid, channel1);
254
255        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(),
256                NotificationChannel.DEFAULT_CHANNEL_ID);
257
258        XmlPullParser parser = Xml.newPullParser();
259        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
260                null);
261        parser.nextTag();
262        mHelper.readXml(parser, false);
263
264        final NotificationChannel updated =
265                mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
266        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
267        assertFalse(updated.canBypassDnd());
268        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,updated.getLockscreenVisibility());
269        assertEquals(0, updated.getUserLockedFields());
270    }
271
272    @Test
273    public void testChannelXml_defaultChannelUpdatedApp() throws Exception {
274        final ApplicationInfo updated = new ApplicationInfo();
275        updated.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
276        when(mPm.getApplicationInfo(anyString(), anyInt())).thenReturn(updated);
277
278        NotificationChannel channel1 =
279                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MIN);
280
281        mHelper.createNotificationChannel(pkg, uid, channel1);
282
283        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(),
284                NotificationChannel.DEFAULT_CHANNEL_ID);
285
286        XmlPullParser parser = Xml.newPullParser();
287        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
288                null);
289        parser.nextTag();
290        mHelper.readXml(parser, false);
291
292        assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
293                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID).getImportance());
294    }
295
296    // TODO: test with hardcoded N xml.
297    @Test
298    public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
299        mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_HIGH);
300        mHelper.setPriority(pkg, uid, Notification.PRIORITY_MAX);
301        mHelper.setVisibilityOverride(pkg, uid, Notification.VISIBILITY_SECRET);
302        // pre-O xml won't have channels.
303        mHelper.deleteNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
304
305        ByteArrayOutputStream baos =
306                writeXmlAndPurge(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
307
308        XmlPullParser parser = Xml.newPullParser();
309        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
310                null);
311        parser.nextTag();
312        mHelper.readXml(parser, false);
313
314        final NotificationChannel updated =
315                mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
316        assertEquals(NotificationManager.IMPORTANCE_HIGH, updated.getImportance());
317        assertTrue(updated.canBypassDnd());
318        assertEquals(Notification.VISIBILITY_SECRET, updated.getLockscreenVisibility());
319        assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
320                | NotificationChannel.USER_LOCKED_PRIORITY
321                | NotificationChannel.USER_LOCKED_VISIBILITY, updated.getUserLockedFields());
322    }
323
324    // TODO: test lock individually.
325    @Test
326    public void testUpdate_userLockedChannelFields() throws Exception {
327        // all fields locked by user
328        final NotificationChannel channel =
329                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
330        channel.setRingtone(new Uri.Builder().scheme("test").build());
331        channel.setLights(true);
332        channel.setBypassDnd(true);
333        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
334        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE
335                | NotificationChannel.USER_LOCKED_VISIBILITY
336                | NotificationChannel.USER_LOCKED_VIBRATION
337                | NotificationChannel.USER_LOCKED_LIGHTS
338                | NotificationChannel.USER_LOCKED_PRIORITY
339                | NotificationChannel.USER_LOCKED_RINGTONE);
340
341        mHelper.createNotificationChannel(pkg, uid, channel);
342
343        // same id, try to update all fields
344        final NotificationChannel channel2 =
345                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
346        channel2.setRingtone(new Uri.Builder().scheme("test2").build());
347        channel2.setLights(false);
348        channel2.setBypassDnd(false);
349        channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
350
351        mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2);
352
353        // no fields should be changed
354        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
355    }
356
357    @Test
358    public void testUpdate() throws Exception {
359        // no fields locked by user
360        final NotificationChannel channel =
361                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
362        channel.setRingtone(new Uri.Builder().scheme("test").build());
363        channel.setLights(true);
364        channel.setBypassDnd(true);
365        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
366
367        mHelper.createNotificationChannel(pkg, uid, channel);
368
369        // same id, try to update all fields
370        final NotificationChannel channel2 =
371                new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
372        channel2.setRingtone(new Uri.Builder().scheme("test2").build());
373        channel2.setLights(false);
374        channel2.setBypassDnd(false);
375        channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
376
377        mHelper.updateNotificationChannel(pkg, uid, channel2);
378
379        // all fields should be changed
380        assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
381    }
382
383    @Test
384    public void testGetChannelWithFallback() throws Exception {
385        NotificationChannel channel =
386                mHelper.getNotificationChannelWithFallback(pkg, uid, "garbage");
387        assertEquals(NotificationChannel.DEFAULT_CHANNEL_ID, channel.getId());
388    }
389}
390