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.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
25 
26 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
27 import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn;
28 
29 import static org.junit.Assert.assertEquals;
30 import static org.junit.Assert.assertFalse;
31 import static org.junit.Assert.assertTrue;
32 import static org.mockito.Mockito.mock;
33 
34 import android.app.ActivityManager;
35 import android.app.Notification;
36 import android.app.NotificationChannel;
37 import android.app.PendingIntent;
38 import android.app.Person;
39 import android.content.Intent;
40 import android.graphics.drawable.Icon;
41 import android.media.session.MediaSession;
42 import android.os.Bundle;
43 import android.os.UserHandle;
44 import android.service.notification.NotificationListenerService.Ranking;
45 import android.service.notification.SnoozeCriterion;
46 import android.service.notification.StatusBarNotification;
47 import android.testing.AndroidTestingRunner;
48 
49 import androidx.test.filters.SmallTest;
50 
51 import com.android.systemui.R;
52 import com.android.systemui.SysuiTestCase;
53 import com.android.systemui.statusbar.RankingBuilder;
54 import com.android.systemui.statusbar.SbnBuilder;
55 import com.android.systemui.util.time.FakeSystemClock;
56 
57 import org.junit.Before;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 
61 import java.util.ArrayList;
62 
63 @SmallTest
64 @RunWith(AndroidTestingRunner.class)
65 public class NotificationEntryTest extends SysuiTestCase {
66     private static final String TEST_PACKAGE_NAME = "test";
67     private static final int TEST_UID = 0;
68     private static final int UID_NORMAL = 123;
69     private static final NotificationChannel NOTIFICATION_CHANNEL =
70             new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE);
71 
72     private int mId;
73 
74     private NotificationEntry mEntry;
75     private final FakeSystemClock mClock = new FakeSystemClock();
76 
77     @Before
setup()78     public void setup() {
79         Notification.Builder n = new Notification.Builder(mContext, "")
80                 .setSmallIcon(R.drawable.ic_person)
81                 .setContentTitle("Title")
82                 .setContentText("Text");
83 
84         mEntry = new NotificationEntryBuilder()
85                 .setPkg(TEST_PACKAGE_NAME)
86                 .setOpPkg(TEST_PACKAGE_NAME)
87                 .setUid(TEST_UID)
88                 .setId(mId++)
89                 .setNotification(n.build())
90                 .setUser(new UserHandle(ActivityManager.getCurrentUser()))
91                 .build();
92     }
93 
94     @Test
testIsExemptFromDndVisualSuppression_foreground()95     public void testIsExemptFromDndVisualSuppression_foreground() {
96         mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
97 
98         assertTrue(mEntry.isExemptFromDndVisualSuppression());
99         assertFalse(mEntry.shouldSuppressAmbient());
100     }
101 
102     @Test
testIsExemptFromDndVisualSuppression_media()103     public void testIsExemptFromDndVisualSuppression_media() {
104         Notification.Builder n = new Notification.Builder(mContext, "")
105                 .setStyle(new Notification.MediaStyle()
106                         .setMediaSession(mock(MediaSession.Token.class)))
107                 .setSmallIcon(R.drawable.ic_person)
108                 .setContentTitle("Title")
109                 .setContentText("Text");
110         NotificationEntry e1 = new NotificationEntryBuilder()
111                 .setNotification(n.build())
112                 .build();
113 
114         assertTrue(e1.isExemptFromDndVisualSuppression());
115         assertFalse(e1.shouldSuppressAmbient());
116     }
117 
118     @Test
testIsExemptFromDndVisualSuppression_system()119     public void testIsExemptFromDndVisualSuppression_system() {
120         mEntry.mIsSystemNotification = true;
121 
122         assertTrue(mEntry.isExemptFromDndVisualSuppression());
123         assertFalse(mEntry.shouldSuppressAmbient());
124     }
125 
126     @Test
testIsNotExemptFromDndVisualSuppression_hiddenCategories()127     public void testIsNotExemptFromDndVisualSuppression_hiddenCategories() {
128         NotificationEntry entry = new NotificationEntryBuilder()
129                 .setUid(UID_NORMAL)
130                 .build();
131         entry.mIsSystemNotification = true;
132         modifyRanking(entry).setSuppressedVisualEffects(SUPPRESSED_EFFECT_AMBIENT).build();
133 
134         modifySbn(entry)
135                 .setNotification(
136                         new Notification.Builder(mContext, "").setCategory(CATEGORY_CALL).build())
137                 .build();
138         assertFalse(entry.isExemptFromDndVisualSuppression());
139         assertTrue(entry.shouldSuppressAmbient());
140 
141         modifySbn(entry)
142                 .setNotification(
143                         new Notification.Builder(mContext, "")
144                                 .setCategory(CATEGORY_REMINDER)
145                                 .build())
146                 .build();
147         assertFalse(entry.isExemptFromDndVisualSuppression());
148 
149         modifySbn(entry)
150                 .setNotification(
151                         new Notification.Builder(mContext, "").setCategory(CATEGORY_ALARM).build())
152                 .build();
153         assertFalse(entry.isExemptFromDndVisualSuppression());
154 
155         modifySbn(entry)
156                 .setNotification(
157                         new Notification.Builder(mContext, "").setCategory(CATEGORY_EVENT).build())
158                 .build();
159         assertFalse(entry.isExemptFromDndVisualSuppression());
160 
161         modifySbn(entry)
162                 .setNotification(
163                         new Notification.Builder(mContext, "")
164                                 .setCategory(CATEGORY_MESSAGE)
165                                 .build())
166                 .build();
167         assertFalse(entry.isExemptFromDndVisualSuppression());
168     }
169 
170     @Test
testCreateNotificationDataEntry_RankingUpdate()171     public void testCreateNotificationDataEntry_RankingUpdate() {
172         StatusBarNotification sbn = new SbnBuilder().build();
173         sbn.getNotification().actions =
174                 new Notification.Action[]{createContextualAction("appGeneratedAction")};
175 
176         ArrayList<Notification.Action> systemGeneratedSmartActions =
177                 createActions("systemGeneratedAction");
178 
179         SnoozeCriterion snoozeCriterion = new SnoozeCriterion("id", "explanation", "confirmation");
180         ArrayList<SnoozeCriterion> snoozeCriterions = new ArrayList<>();
181         snoozeCriterions.add(snoozeCriterion);
182 
183         Ranking ranking = new RankingBuilder()
184                 .setKey(sbn.getKey())
185                 .setSmartActions(systemGeneratedSmartActions)
186                 .setChannel(NOTIFICATION_CHANNEL)
187                 .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
188                 .setSnoozeCriteria(snoozeCriterions)
189                 .build();
190 
191         NotificationEntry entry =
192                 new NotificationEntry(sbn, ranking, mClock.uptimeMillis());
193 
194         assertEquals(systemGeneratedSmartActions, entry.getSmartActions());
195         assertEquals(NOTIFICATION_CHANNEL, entry.getChannel());
196         assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.getUserSentiment());
197         assertEquals(snoozeCriterions, entry.getSnoozeCriteria());
198     }
199 
200     @Test
notificationDataEntry_testIsLastMessageFromReply()201     public void notificationDataEntry_testIsLastMessageFromReply() {
202         Person.Builder person = new Person.Builder()
203                 .setName("name")
204                 .setKey("abc")
205                 .setUri("uri")
206                 .setBot(true);
207 
208         // EXTRA_MESSAGING_PERSON is the same Person as the sender in last message in EXTRA_MESSAGES
209         Bundle bundle = new Bundle();
210         bundle.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person.build());
211         Bundle[] messagesBundle = new Bundle[]{new Notification.MessagingStyle.Message(
212                 "text", 0, person.build()).toBundle()};
213         bundle.putParcelableArray(Notification.EXTRA_MESSAGES, messagesBundle);
214 
215         Notification notification = new Notification.Builder(mContext, "test")
216                 .addExtras(bundle)
217                 .build();
218 
219         NotificationEntry entry = new NotificationEntryBuilder()
220                 .setPkg("pkg")
221                 .setOpPkg("pkg")
222                 .setTag("tag")
223                 .setNotification(notification)
224                 .setUser(mContext.getUser())
225                 .setOverrideGroupKey("")
226                 .build();
227         entry.setHasSentReply();
228 
229         assertTrue(entry.isLastMessageFromReply());
230     }
231 
createContextualAction(String title)232     private Notification.Action createContextualAction(String title) {
233         return new Notification.Action.Builder(
234                 Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
235                 title,
236                 PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0))
237                 .setContextual(true)
238                 .build();
239     }
240 
createAction(String title)241     private Notification.Action createAction(String title) {
242         return new Notification.Action.Builder(
243                 Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
244                 title,
245                 PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
246     }
247 
createActions(String... titles)248     private ArrayList<Notification.Action> createActions(String... titles) {
249         ArrayList<Notification.Action> actions = new ArrayList<>();
250         for (String title : titles) {
251             actions.add(createAction(title));
252         }
253         return actions;
254     }
255 }
256