• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.notification.collection;
18 
19 import static android.app.Notification.CATEGORY_ALARM;
20 import static android.app.Notification.CATEGORY_CALL;
21 import static android.app.Notification.CATEGORY_EVENT;
22 import static android.app.Notification.CATEGORY_MESSAGE;
23 import static android.app.Notification.CATEGORY_REMINDER;
24 import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
25 import static android.app.Notification.FLAG_PROMOTED_ONGOING;
26 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
27 
28 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
29 import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn;
30 
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertFalse;
33 import static org.junit.Assert.assertTrue;
34 import static org.mockito.Mockito.doReturn;
35 
36 import android.app.ActivityManager;
37 import android.app.Notification;
38 import android.app.NotificationChannel;
39 import android.app.PendingIntent;
40 import android.app.Person;
41 import android.content.Intent;
42 import android.graphics.drawable.Icon;
43 import android.media.session.MediaSession;
44 import android.os.Bundle;
45 import android.os.UserHandle;
46 import android.platform.test.annotations.DisableFlags;
47 import android.platform.test.annotations.EnableFlags;
48 import android.platform.test.flag.junit.SetFlagsRule;
49 import android.service.notification.NotificationListenerService.Ranking;
50 import android.service.notification.SnoozeCriterion;
51 import android.service.notification.StatusBarNotification;
52 
53 import androidx.test.ext.junit.runners.AndroidJUnit4;
54 import androidx.test.filters.SmallTest;
55 
56 import com.android.systemui.SysuiTestCase;
57 import com.android.systemui.res.R;
58 import com.android.systemui.statusbar.RankingBuilder;
59 import com.android.systemui.statusbar.SbnBuilder;
60 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
61 import com.android.systemui.statusbar.notification.collection.UseElapsedRealtimeForCreationTime;
62 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
63 import com.android.systemui.util.time.FakeSystemClock;
64 
65 import org.junit.Before;
66 import org.junit.Rule;
67 import org.junit.Test;
68 import org.junit.runner.RunWith;
69 import org.mockito.Mockito;
70 
71 import java.util.ArrayList;
72 
73 @SmallTest
74 @RunWith(AndroidJUnit4.class)
75 public class NotificationEntryTest extends SysuiTestCase {
76     private static final String TEST_PACKAGE_NAME = "test";
77     private static final int TEST_UID = 0;
78     private static final int UID_NORMAL = 123;
79     private static final NotificationChannel NOTIFICATION_CHANNEL =
80             new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE);
81 
82     private int mId;
83 
84     private NotificationEntry mEntry;
85     private NotificationChannel mChannel = Mockito.mock(NotificationChannel.class);
86     private final FakeSystemClock mClock = new FakeSystemClock();
87 
88     @Rule
89     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
90 
91     @Before
setup()92     public void setup() {
93         Notification.Builder n = new Notification.Builder(mContext, "")
94                 .setSmallIcon(R.drawable.ic_person)
95                 .setContentTitle("Title")
96                 .setContentText("Text");
97 
98         mEntry = new NotificationEntryBuilder()
99                 .setPkg(TEST_PACKAGE_NAME)
100                 .setOpPkg(TEST_PACKAGE_NAME)
101                 .setUid(TEST_UID)
102                 .setChannel(mChannel)
103                 .setId(mId++)
104                 .setNotification(n.build())
105                 .setUser(new UserHandle(ActivityManager.getCurrentUser()))
106                 .build();
107 
108         doReturn(false).when(mChannel).isBlockable();
109     }
110 
111     @Test
testIsExemptFromDndVisualSuppression_foreground()112     public void testIsExemptFromDndVisualSuppression_foreground() {
113         mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
114 
115         assertTrue(mEntry.isExemptFromDndVisualSuppression());
116         assertFalse(mEntry.shouldSuppressAmbient());
117     }
118 
119     @Test
testBlockableEntryWhenCritical()120     public void testBlockableEntryWhenCritical() {
121         doReturn(true).when(mChannel).isBlockable();
122         mEntry.setRanking(mEntry.getRanking());
123 
124         assertTrue(mEntry.isBlockable());
125     }
126 
127 
128     @Test
testBlockableEntryWhenCriticalAndChannelNotBlockable()129     public void testBlockableEntryWhenCriticalAndChannelNotBlockable() {
130         doReturn(true).when(mChannel).isBlockable();
131         doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
132         mEntry.setRanking(mEntry.getRanking());
133 
134         assertTrue(mEntry.isBlockable());
135     }
136 
137     @Test
testNonBlockableEntryWhenCriticalAndChannelNotBlockable()138     public void testNonBlockableEntryWhenCriticalAndChannelNotBlockable() {
139         doReturn(false).when(mChannel).isBlockable();
140         doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
141         mEntry.setRanking(mEntry.getRanking());
142 
143         assertFalse(mEntry.isBlockable());
144     }
145 
146     @Test
testBlockableWhenEntryHasNoChannel()147     public void testBlockableWhenEntryHasNoChannel() {
148         StatusBarNotification sbn = new SbnBuilder().build();
149         Ranking ranking = new RankingBuilder()
150                 .setChannel(null)
151                 .setKey(sbn.getKey())
152                 .build();
153 
154         NotificationEntry entry =
155                 new NotificationEntry(sbn, ranking,
156                         UseElapsedRealtimeForCreationTime.getCurrentTime(mClock));
157 
158         assertFalse(entry.isBlockable());
159     }
160 
161     @Test
testIsExemptFromDndVisualSuppression_media()162     public void testIsExemptFromDndVisualSuppression_media() {
163         MediaSession session = new MediaSession(mContext, "test");
164         Notification.Builder n = new Notification.Builder(mContext, "")
165                 .setStyle(new Notification.MediaStyle()
166                         .setMediaSession(session.getSessionToken()))
167                 .setSmallIcon(R.drawable.ic_person)
168                 .setContentTitle("Title")
169                 .setContentText("Text");
170         NotificationEntry e1 = new NotificationEntryBuilder()
171                 .setNotification(n.build())
172                 .build();
173 
174         assertTrue(e1.isExemptFromDndVisualSuppression());
175         assertFalse(e1.shouldSuppressAmbient());
176     }
177 
178     @Test
testIsExemptFromDndVisualSuppression_system()179     public void testIsExemptFromDndVisualSuppression_system() {
180         doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
181         doReturn(false).when(mChannel).isBlockable();
182 
183         mEntry.setRanking(mEntry.getRanking());
184 
185         assertFalse(mEntry.isBlockable());
186         assertTrue(mEntry.isExemptFromDndVisualSuppression());
187         assertFalse(mEntry.shouldSuppressAmbient());
188     }
189 
190     @Test
testIsNotExemptFromDndVisualSuppression_hiddenCategories()191     public void testIsNotExemptFromDndVisualSuppression_hiddenCategories() {
192         NotificationEntry entry = new NotificationEntryBuilder()
193                 .setUid(UID_NORMAL)
194                 .build();
195         doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
196         modifyRanking(entry).setSuppressedVisualEffects(SUPPRESSED_EFFECT_AMBIENT).build();
197 
198         modifySbn(entry)
199                 .setNotification(
200                         new Notification.Builder(mContext, "").setCategory(CATEGORY_CALL).build())
201                 .build();
202         assertFalse(entry.isExemptFromDndVisualSuppression());
203         assertTrue(entry.shouldSuppressAmbient());
204 
205         modifySbn(entry)
206                 .setNotification(
207                         new Notification.Builder(mContext, "")
208                                 .setCategory(CATEGORY_REMINDER)
209                                 .build())
210                 .build();
211         assertFalse(entry.isExemptFromDndVisualSuppression());
212 
213         modifySbn(entry)
214                 .setNotification(
215                         new Notification.Builder(mContext, "").setCategory(CATEGORY_ALARM).build())
216                 .build();
217         assertFalse(entry.isExemptFromDndVisualSuppression());
218 
219         modifySbn(entry)
220                 .setNotification(
221                         new Notification.Builder(mContext, "").setCategory(CATEGORY_EVENT).build())
222                 .build();
223         assertFalse(entry.isExemptFromDndVisualSuppression());
224 
225         modifySbn(entry)
226                 .setNotification(
227                         new Notification.Builder(mContext, "")
228                                 .setCategory(CATEGORY_MESSAGE)
229                                 .build())
230                 .build();
231         assertFalse(entry.isExemptFromDndVisualSuppression());
232     }
233 
234     @Test
testCreateNotificationDataEntry_RankingUpdate()235     public void testCreateNotificationDataEntry_RankingUpdate() {
236         StatusBarNotification sbn = new SbnBuilder().build();
237         sbn.getNotification().actions =
238                 new Notification.Action[]{createContextualAction("appGeneratedAction")};
239 
240         ArrayList<Notification.Action> systemGeneratedSmartActions =
241                 createActions("systemGeneratedAction");
242 
243         SnoozeCriterion snoozeCriterion = new SnoozeCriterion("id", "explanation", "confirmation");
244         ArrayList<SnoozeCriterion> snoozeCriterions = new ArrayList<>();
245         snoozeCriterions.add(snoozeCriterion);
246 
247         Ranking ranking = new RankingBuilder()
248                 .setKey(sbn.getKey())
249                 .setSmartActions(systemGeneratedSmartActions)
250                 .setChannel(NOTIFICATION_CHANNEL)
251                 .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
252                 .setSnoozeCriteria(snoozeCriterions)
253                 .build();
254 
255         NotificationEntry entry =
256                 new NotificationEntry(sbn, ranking,
257                         UseElapsedRealtimeForCreationTime.getCurrentTime(mClock));
258 
259         assertEquals(systemGeneratedSmartActions, entry.getSmartActions());
260         assertEquals(NOTIFICATION_CHANNEL, entry.getChannel());
261         assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.getUserSentiment());
262         assertEquals(snoozeCriterions, entry.getSnoozeCriteria());
263     }
264 
265     @Test
testIsStickyAndNotDemoted_noFlagAndDemoted_returnFalse()266     public void testIsStickyAndNotDemoted_noFlagAndDemoted_returnFalse() {
267         mEntry.getSbn().getNotification().flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
268         assertFalse(mEntry.isStickyAndNotDemoted());
269     }
270 
271     @Test
testIsStickyAndNotDemoted_noFlagAndNotDemoted_demoteAndReturnFalse()272     public void testIsStickyAndNotDemoted_noFlagAndNotDemoted_demoteAndReturnFalse() {
273         mEntry.getSbn().getNotification().flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
274 
275         assertFalse(mEntry.isStickyAndNotDemoted());
276         assertTrue(mEntry.isDemoted());
277     }
278 
279     @Test
testIsStickyAndNotDemoted_hasFlagButAlreadyDemoted_returnFalse()280     public void testIsStickyAndNotDemoted_hasFlagButAlreadyDemoted_returnFalse() {
281         mEntry.getSbn().getNotification().flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
282         mEntry.demoteStickyHun();
283 
284         assertFalse(mEntry.isStickyAndNotDemoted());
285     }
286 
287     @Test
testIsStickyAndNotDemoted_hasFlagAndNotDemoted_returnTrue()288     public void testIsStickyAndNotDemoted_hasFlagAndNotDemoted_returnTrue() {
289         mEntry.getSbn().getNotification().flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
290 
291         assertFalse(mEntry.isDemoted());
292         assertTrue(mEntry.isStickyAndNotDemoted());
293     }
294 
295     @Test
296     @EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
isPromotedOngoing_noFlagOnNotif_false()297     public void isPromotedOngoing_noFlagOnNotif_false() {
298         mEntry.getSbn().getNotification().flags &= ~FLAG_PROMOTED_ONGOING;
299 
300         assertFalse(mEntry.isPromotedOngoing());
301     }
302 
303     @Test
304     @DisableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
isPromotedOngoing_statusBarNotifChipsFlagAndUiFlagOff_false()305     public void isPromotedOngoing_statusBarNotifChipsFlagAndUiFlagOff_false() {
306         mEntry.getSbn().getNotification().flags |= FLAG_PROMOTED_ONGOING;
307 
308         assertFalse(mEntry.isPromotedOngoing());
309     }
310 
311     @Test
testIsNotificationVisibilityPrivate_true()312     public void testIsNotificationVisibilityPrivate_true() {
313         assertTrue(mEntry.isNotificationVisibilityPrivate());
314     }
315 
316     @Test
testIsNotificationVisibilityPrivate_visibilityPublic_false()317     public void testIsNotificationVisibilityPrivate_visibilityPublic_false() {
318         Notification.Builder notification = new Notification.Builder(mContext, "")
319                 .setVisibility(Notification.VISIBILITY_PUBLIC)
320                 .setSmallIcon(R.drawable.ic_person)
321                 .setContentTitle("Title")
322                 .setContentText("Text");
323 
324         NotificationEntry entry = new NotificationEntryBuilder()
325                 .setPkg(TEST_PACKAGE_NAME)
326                 .setOpPkg(TEST_PACKAGE_NAME)
327                 .setUid(TEST_UID)
328                 .setChannel(mChannel)
329                 .setId(mId++)
330                 .setNotification(notification.build())
331                 .setUser(new UserHandle(ActivityManager.getCurrentUser()))
332                 .build();
333 
334         assertFalse(entry.isNotificationVisibilityPrivate());
335     }
336 
337     @Test
testIsChannelVisibilityPrivate_true()338     public void testIsChannelVisibilityPrivate_true() {
339         assertTrue(mEntry.isChannelVisibilityPrivate());
340     }
341 
342     @Test
testIsChannelVisibilityPrivate_visibilityPublic_false()343     public void testIsChannelVisibilityPrivate_visibilityPublic_false() {
344         NotificationChannel channel =
345                 new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE);
346         channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
347         StatusBarNotification sbn = new SbnBuilder().build();
348         Ranking ranking = new RankingBuilder()
349                 .setChannel(channel)
350                 .setKey(sbn.getKey())
351                 .build();
352         NotificationEntry entry =
353                 new NotificationEntry(sbn, ranking,
354                         UseElapsedRealtimeForCreationTime.getCurrentTime(mClock));
355 
356         assertFalse(entry.isChannelVisibilityPrivate());
357     }
358 
359     @Test
testIsChannelVisibilityPrivate_entryHasNoChannel_false()360     public void testIsChannelVisibilityPrivate_entryHasNoChannel_false() {
361         StatusBarNotification sbn = new SbnBuilder().build();
362         Ranking ranking = new RankingBuilder()
363                 .setChannel(null)
364                 .setKey(sbn.getKey())
365                 .build();
366         NotificationEntry entry =
367                 new NotificationEntry(sbn, ranking,
368                         UseElapsedRealtimeForCreationTime.getCurrentTime(mClock));
369 
370         assertFalse(entry.isChannelVisibilityPrivate());
371     }
372 
373     @Test
notificationDataEntry_testIsLastMessageFromReply()374     public void notificationDataEntry_testIsLastMessageFromReply() {
375         Person.Builder person = new Person.Builder()
376                 .setName("name")
377                 .setKey("abc")
378                 .setUri("uri")
379                 .setBot(true);
380 
381         // EXTRA_MESSAGING_PERSON is the same Person as the sender in last message in EXTRA_MESSAGES
382         Bundle bundle = new Bundle();
383         bundle.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person.build());
384         Bundle[] messagesBundle = new Bundle[]{new Notification.MessagingStyle.Message(
385                 "text", 0, person.build()).toBundle()};
386         bundle.putParcelableArray(Notification.EXTRA_MESSAGES, messagesBundle);
387 
388         Notification notification = new Notification.Builder(mContext, "test")
389                 .addExtras(bundle)
390                 .build();
391 
392         NotificationEntry entry = new NotificationEntryBuilder()
393                 .setPkg("pkg")
394                 .setOpPkg("pkg")
395                 .setTag("tag")
396                 .setNotification(notification)
397                 .setUser(mContext.getUser())
398                 .setOverrideGroupKey("")
399                 .build();
400         entry.setHasSentReply();
401 
402         assertTrue(entry.isLastMessageFromReply());
403     }
404 
405     @Test
notificationDataEntry_testIsLastMessageFromReply_invalidPerson_noCrash()406     public void notificationDataEntry_testIsLastMessageFromReply_invalidPerson_noCrash() {
407         Person.Builder person = new Person.Builder()
408                 .setName("name")
409                 .setKey("abc")
410                 .setUri("uri")
411                 .setBot(true);
412 
413         Bundle bundle = new Bundle();
414         // should be Person.class
415         bundle.putParcelable(Notification.EXTRA_MESSAGING_PERSON, new Bundle());
416         Bundle[] messagesBundle = new Bundle[]{new Notification.MessagingStyle.Message(
417                 "text", 0, person.build()).toBundle()};
418         bundle.putParcelableArray(Notification.EXTRA_MESSAGES, messagesBundle);
419 
420         Notification notification = new Notification.Builder(mContext, "test")
421                 .addExtras(bundle)
422                 .build();
423 
424         NotificationEntry entry = new NotificationEntryBuilder()
425                 .setPkg("pkg")
426                 .setOpPkg("pkg")
427                 .setTag("tag")
428                 .setNotification(notification)
429                 .setUser(mContext.getUser())
430                 .setOverrideGroupKey("")
431                 .build();
432         entry.setHasSentReply();
433 
434         entry.isLastMessageFromReply();
435 
436         // no crash, good
437     }
438 
createContextualAction(String title)439     private Notification.Action createContextualAction(String title) {
440         return new Notification.Action.Builder(
441                 Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
442                 title,
443                 PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"),
444                     PendingIntent.FLAG_IMMUTABLE))
445                 .setContextual(true)
446                 .build();
447     }
448 
createAction(String title)449     private Notification.Action createAction(String title) {
450         return new Notification.Action.Builder(
451                 Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
452                 title,
453                 PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"),
454                     PendingIntent.FLAG_IMMUTABLE)).build();
455     }
456 
createActions(String... titles)457     private ArrayList<Notification.Action> createActions(String... titles) {
458         ArrayList<Notification.Action> actions = new ArrayList<>();
459         for (String title : titles) {
460             actions.add(createAction(title));
461         }
462         return actions;
463     }
464 }
465