1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.notification;
17 
18 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
19 import static android.app.NotificationManager.IMPORTANCE_LOW;
20 
21 import static junit.framework.TestCase.assertEquals;
22 
23 import static org.junit.Assert.assertTrue;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.Matchers.anyInt;
26 import static org.mockito.Matchers.eq;
27 import static org.mockito.Mockito.mock;
28 import static org.mockito.Mockito.when;
29 
30 import android.app.Notification;
31 import android.app.NotificationChannel;
32 import android.app.NotificationManager;
33 import android.content.ContentProvider;
34 import android.content.Context;
35 import android.content.IContentProvider;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.PackageInfo;
38 import android.content.pm.PackageManager;
39 import android.content.pm.Signature;
40 import android.media.AudioAttributes;
41 import android.net.Uri;
42 import android.os.Build;
43 import android.os.UserHandle;
44 import android.os.Vibrator;
45 import android.service.notification.StatusBarNotification;
46 import android.test.suitebuilder.annotation.SmallTest;
47 import android.testing.TestableContentResolver;
48 
49 import androidx.test.InstrumentationRegistry;
50 import androidx.test.runner.AndroidJUnit4;
51 
52 import com.android.server.UiServiceTestCase;
53 
54 import org.junit.Before;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 import org.mockito.Mock;
58 import org.mockito.MockitoAnnotations;
59 
60 import java.util.ArrayList;
61 
62 @SmallTest
63 @RunWith(AndroidJUnit4.class)
64 public class RankingHelperTest extends UiServiceTestCase {
65     private static final String PKG = "com.android.server.notification";
66     private static final int UID = 0;
67     private static final UserHandle USER = UserHandle.of(0);
68     private static final String UPDATED_PKG = "updatedPkg";
69     private static final int UID2 = 1111;
70     private static final String SYSTEM_PKG = "android";
71     private static final int SYSTEM_UID= 1000;
72     private static final UserHandle USER2 = UserHandle.of(10);
73     private static final String TEST_CHANNEL_ID = "test_channel_id";
74     private static final String TEST_AUTHORITY = "test";
75     private static final Uri SOUND_URI =
76             Uri.parse("content://" + TEST_AUTHORITY + "/internal/audio/media/10");
77     private static final Uri CANONICAL_SOUND_URI =
78             Uri.parse("content://" + TEST_AUTHORITY
79                     + "/internal/audio/media/10?title=Test&canonical=1");
80 
81     @Mock NotificationUsageStats mUsageStats;
82     @Mock RankingHandler mHandler;
83     @Mock PackageManager mPm;
84     @Mock IContentProvider mTestIContentProvider;
85     @Mock Context mContext;
86     @Mock ZenModeHelper mMockZenModeHelper;
87     @Mock RankingConfig mConfig;
88     @Mock Vibrator mVibrator;
89 
90     private NotificationManager.Policy mTestNotificationPolicy;
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     @Before
setUp()105     public void setUp() throws Exception {
106         MockitoAnnotations.initMocks(this);
107         UserHandle user = UserHandle.ALL;
108 
109         final ApplicationInfo legacy = new ApplicationInfo();
110         legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
111         final ApplicationInfo upgrade = new ApplicationInfo();
112         upgrade.targetSdkVersion = Build.VERSION_CODES.O;
113         when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
114         when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
115         when(mPm.getApplicationInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(upgrade);
116         when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
117         when(mPm.getPackageUidAsUser(eq(UPDATED_PKG), anyInt())).thenReturn(UID2);
118         when(mPm.getPackageUidAsUser(eq(SYSTEM_PKG), anyInt())).thenReturn(SYSTEM_UID);
119         PackageInfo info = mock(PackageInfo.class);
120         info.signatures = new Signature[] {mock(Signature.class)};
121         when(mPm.getPackageInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(info);
122         when(mPm.getPackageInfoAsUser(eq(PKG), anyInt(), anyInt()))
123                 .thenReturn(mock(PackageInfo.class));
124         when(mContext.getResources()).thenReturn(
125                 InstrumentationRegistry.getContext().getResources());
126         when(mContext.getContentResolver()).thenReturn(
127                 InstrumentationRegistry.getContext().getContentResolver());
128         when(mContext.getPackageManager()).thenReturn(mPm);
129         when(mContext.getApplicationInfo()).thenReturn(legacy);
130         when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator);
131         TestableContentResolver contentResolver = getContext().getContentResolver();
132         contentResolver.setFallbackToExisting(false);
133 
134         ContentProvider testContentProvider = mock(ContentProvider.class);
135         when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
136         contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
137 
138         when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
139                 .thenReturn(CANONICAL_SOUND_URI);
140         when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
141                 .thenReturn(CANONICAL_SOUND_URI);
142         when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
143                 .thenReturn(SOUND_URI);
144 
145         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
146                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
147         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
148         mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
149                 mUsageStats, new String[] {ImportanceExtractor.class.getName()});
150 
151         mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
152                 .setContentTitle("A")
153                 .setGroup("G")
154                 .setSortKey("A")
155                 .setWhen(1205)
156                 .build();
157         mRecordGroupGSortA = new NotificationRecord(mContext, new StatusBarNotification(
158                 PKG, PKG, 1, null, 0, 0, mNotiGroupGSortA, user,
159                 null, System.currentTimeMillis()), getLowChannel());
160 
161         mNotiGroupGSortB = new Notification.Builder(mContext, TEST_CHANNEL_ID)
162                 .setContentTitle("B")
163                 .setGroup("G")
164                 .setSortKey("B")
165                 .setWhen(1200)
166                 .build();
167         mRecordGroupGSortB = new NotificationRecord(mContext, new StatusBarNotification(
168                 PKG, PKG, 1, null, 0, 0, mNotiGroupGSortB, user,
169                 null, System.currentTimeMillis()), getLowChannel());
170 
171         mNotiNoGroup = new Notification.Builder(mContext, TEST_CHANNEL_ID)
172                 .setContentTitle("C")
173                 .setWhen(1201)
174                 .build();
175         mRecordNoGroup = new NotificationRecord(mContext, new StatusBarNotification(
176                 PKG, PKG, 1, null, 0, 0, mNotiNoGroup, user,
177                 null, System.currentTimeMillis()), getLowChannel());
178 
179         mNotiNoGroup2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
180                 .setContentTitle("D")
181                 .setWhen(1202)
182                 .build();
183         mRecordNoGroup2 = new NotificationRecord(mContext, new StatusBarNotification(
184                 PKG, PKG, 1, null, 0, 0, mNotiNoGroup2, user,
185                 null, System.currentTimeMillis()), getLowChannel());
186 
187         mNotiNoGroupSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
188                 .setContentTitle("E")
189                 .setWhen(1201)
190                 .setSortKey("A")
191                 .build();
192         mRecordNoGroupSortA = new NotificationRecord(mContext, new StatusBarNotification(
193                 PKG, PKG, 1, null, 0, 0, mNotiNoGroupSortA, user,
194                 null, System.currentTimeMillis()), getLowChannel());
195 
196         mAudioAttributes = new AudioAttributes.Builder()
197                 .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
198                 .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
199                 .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
200                 .build();
201     }
202 
getLowChannel()203     private NotificationChannel getLowChannel() {
204         return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
205                 IMPORTANCE_LOW);
206     }
207 
getDefaultChannel()208     private NotificationChannel getDefaultChannel() {
209         return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
210                 IMPORTANCE_DEFAULT);
211     }
212 
213     @Test
testSortShouldRespectCritical()214     public void testSortShouldRespectCritical() throws Exception {
215         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(7);
216         NotificationRecord critical = generateRecord(0);
217         NotificationRecord critical_ish = generateRecord(1);
218         NotificationRecord critical_notAtAll = generateRecord(100);
219 
220         notificationList.add(critical_ish);
221         notificationList.add(mRecordGroupGSortA);
222         notificationList.add(critical_notAtAll);
223         notificationList.add(mRecordGroupGSortB);
224         notificationList.add(mRecordNoGroup);
225         notificationList.add(mRecordNoGroupSortA);
226         notificationList.add(critical);
227         mHelper.sort(notificationList);
228 
229         assertTrue(mHelper.indexOf(notificationList, critical) == 0);
230         assertTrue(mHelper.indexOf(notificationList, critical_ish) == 1);
231         assertTrue(mHelper.indexOf(notificationList, critical_notAtAll) == 6);
232     }
233 
generateRecord(int criticality)234     private NotificationRecord generateRecord(int criticality) {
235         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
236         final Notification.Builder builder = new Notification.Builder(getContext())
237                 .setContentTitle("foo")
238                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
239         Notification n = builder.build();
240         StatusBarNotification sbn = new StatusBarNotification("", "", 0, "", 0,
241                 0, n, UserHandle.ALL, null, System.currentTimeMillis());
242         NotificationRecord notificationRecord = new NotificationRecord(getContext(), sbn, channel);
243         notificationRecord.setCriticality(criticality);
244         return notificationRecord;
245     }
246 
247     @Test
testFindAfterRankingWithASplitGroup()248     public void testFindAfterRankingWithASplitGroup() throws Exception {
249         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(4);
250         notificationList.add(mRecordGroupGSortA);
251         notificationList.add(mRecordGroupGSortB);
252         notificationList.add(mRecordNoGroup);
253         notificationList.add(mRecordNoGroupSortA);
254         mHelper.sort(notificationList);
255         assertTrue(mHelper.indexOf(notificationList, mRecordGroupGSortA) >= 0);
256         assertTrue(mHelper.indexOf(notificationList, mRecordGroupGSortB) >= 0);
257         assertTrue(mHelper.indexOf(notificationList, mRecordNoGroup) >= 0);
258         assertTrue(mHelper.indexOf(notificationList, mRecordNoGroupSortA) >= 0);
259     }
260 
261     @Test
testSortShouldNotThrowWithPlainNotifications()262     public void testSortShouldNotThrowWithPlainNotifications() throws Exception {
263         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(2);
264         notificationList.add(mRecordNoGroup);
265         notificationList.add(mRecordNoGroup2);
266         mHelper.sort(notificationList);
267     }
268 
269     @Test
testSortShouldNotThrowOneSorted()270     public void testSortShouldNotThrowOneSorted() throws Exception {
271         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(2);
272         notificationList.add(mRecordNoGroup);
273         notificationList.add(mRecordNoGroupSortA);
274         mHelper.sort(notificationList);
275     }
276 
277     @Test
testSortShouldNotThrowOneNotification()278     public void testSortShouldNotThrowOneNotification() throws Exception {
279         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(1);
280         notificationList.add(mRecordNoGroup);
281         mHelper.sort(notificationList);
282     }
283 
284     @Test
testSortShouldNotThrowOneSortKey()285     public void testSortShouldNotThrowOneSortKey() throws Exception {
286         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(1);
287         notificationList.add(mRecordGroupGSortB);
288         mHelper.sort(notificationList);
289     }
290 
291     @Test
testSortShouldNotThrowOnEmptyList()292     public void testSortShouldNotThrowOnEmptyList() throws Exception {
293         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>();
294         mHelper.sort(notificationList);
295     }
296 
297     @Test
testGroupNotifications_highestIsProxy()298     public void testGroupNotifications_highestIsProxy() {
299         ArrayList<NotificationRecord> notificationList = new ArrayList<>();
300         // this should be the last in the list, except it's in a group with a high child
301         Notification lowSummaryN = new Notification.Builder(mContext, "")
302                 .setGroup("group")
303                 .setGroupSummary(true)
304                 .build();
305         NotificationRecord lowSummary = new NotificationRecord(mContext, new StatusBarNotification(
306                 PKG, PKG, 1, "summary", 0, 0, lowSummaryN, USER,
307                 null, System.currentTimeMillis()), getLowChannel());
308         notificationList.add(lowSummary);
309 
310         Notification lowN = new Notification.Builder(mContext, "").build();
311         NotificationRecord low = new NotificationRecord(mContext, new StatusBarNotification(
312                 PKG, PKG, 1, "low", 0, 0, lowN, USER,
313                 null, System.currentTimeMillis()), getLowChannel());
314         low.setContactAffinity(0.5f);
315         notificationList.add(low);
316 
317         Notification highChildN = new Notification.Builder(mContext, "")
318                 .setGroup("group")
319                 .setGroupSummary(false)
320                 .build();
321         NotificationRecord highChild = new NotificationRecord(mContext, new StatusBarNotification(
322                 PKG, PKG, 1, "child", 0, 0, highChildN, USER,
323                 null, System.currentTimeMillis()), getDefaultChannel());
324         notificationList.add(highChild);
325 
326         mHelper.sort(notificationList);
327 
328         assertEquals(lowSummary, notificationList.get(0));
329         assertEquals(highChild, notificationList.get(1));
330         assertEquals(low, notificationList.get(2));
331     }
332 }
333