1 /*
2  * Copyright (C) 2016 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 com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT;
19 import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static junit.framework.Assert.assertEquals;
24 import static junit.framework.Assert.assertFalse;
25 import static junit.framework.Assert.assertNotNull;
26 import static junit.framework.Assert.assertNull;
27 import static junit.framework.Assert.assertTrue;
28 
29 import static org.mockito.ArgumentMatchers.anyBoolean;
30 import static org.mockito.ArgumentMatchers.eq;
31 import static org.mockito.Matchers.any;
32 import static org.mockito.Matchers.anyInt;
33 import static org.mockito.Matchers.anyLong;
34 import static org.mockito.Mockito.never;
35 import static org.mockito.Mockito.reset;
36 import static org.mockito.Mockito.times;
37 import static org.mockito.Mockito.verify;
38 import static org.mockito.Mockito.when;
39 
40 import android.app.AlarmManager;
41 import android.app.Notification;
42 import android.app.NotificationChannel;
43 import android.app.NotificationManager;
44 import android.app.PendingIntent;
45 import android.os.UserHandle;
46 import android.service.notification.StatusBarNotification;
47 import android.test.suitebuilder.annotation.SmallTest;
48 import android.util.IntArray;
49 import android.util.Xml;
50 
51 import androidx.test.runner.AndroidJUnit4;
52 
53 import com.android.modules.utils.TypedXmlPullParser;
54 import com.android.modules.utils.TypedXmlSerializer;
55 import com.android.server.UiServiceTestCase;
56 import com.android.server.pm.PackageManagerService;
57 
58 import org.junit.Before;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 import org.mockito.ArgumentCaptor;
62 import org.mockito.Mock;
63 import org.mockito.MockitoAnnotations;
64 import org.xmlpull.v1.XmlPullParserException;
65 
66 import java.io.BufferedInputStream;
67 import java.io.BufferedOutputStream;
68 import java.io.ByteArrayInputStream;
69 import java.io.ByteArrayOutputStream;
70 import java.io.IOException;
71 
72 @SmallTest
73 @RunWith(AndroidJUnit4.class)
74 public class SnoozeHelperTest extends UiServiceTestCase {
75     private static final String TEST_CHANNEL_ID = "test_channel_id";
76 
77     private static final String XML_TAG_NAME = "snoozed-notifications";
78     private static final String XML_SNOOZED_NOTIFICATION = "notification";
79     private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context";
80     private static final String XML_SNOOZED_NOTIFICATION_KEY = "key";
81     private static final String XML_SNOOZED_NOTIFICATION_TIME = "time";
82     private static final String XML_SNOOZED_NOTIFICATION_CONTEXT_ID = "id";
83     private static final String XML_SNOOZED_NOTIFICATION_VERSION_LABEL = "version";
84 
85     @Mock SnoozeHelper.Callback mCallback;
86     @Mock AlarmManager mAm;
87     @Mock ManagedServices.UserProfiles mUserProfiles;
88 
89     private SnoozeHelper mSnoozeHelper;
90 
91     @Before
setUp()92     public void setUp() {
93         MockitoAnnotations.initMocks(this);
94 
95         mSnoozeHelper = new SnoozeHelper(getContext(), mCallback, mUserProfiles);
96         mSnoozeHelper.setAlarmManager(mAm);
97     }
98 
99     @Test
testWriteXMLformattedCorrectly_testReadingCorrectTime()100     public void testWriteXMLformattedCorrectly_testReadingCorrectTime()
101             throws XmlPullParserException, IOException {
102         final String max_time_str = Long.toString(Long.MAX_VALUE);
103         final String xml_string = "<snoozed-notifications>"
104                 + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
105                 + "pkg=\"pkg\" key=\"key\" time=\"" + max_time_str + "\"/>"
106                 + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
107                 + "pkg=\"pkg\" key=\"key2\" time=\"" + max_time_str + "\"/>"
108                 + "</snoozed-notifications>";
109         TypedXmlPullParser parser = Xml.newFastPullParser();
110         parser.setInput(new BufferedInputStream(
111                 new ByteArrayInputStream(xml_string.getBytes())), null);
112         mSnoozeHelper.readXml(parser, 1);
113         assertEquals((long) Long.MAX_VALUE, (long) mSnoozeHelper
114                 .getSnoozeTimeForUnpostedNotification(0, "pkg", "key"));
115         verify(mAm, never()).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
116     }
117 
118     @Test
testWriteXML_afterReading_noNPE()119     public void testWriteXML_afterReading_noNPE()
120             throws XmlPullParserException, IOException {
121         final String max_time_str = Long.toString(Long.MAX_VALUE);
122         final String xml_string = "<snoozed-notifications>"
123                 + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
124                 + "pkg=\"pkg\" key=\"key\" time=\"" + max_time_str + "\"/>"
125                 + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
126                 + "pkg=\"pkg\" key=\"key2\" time=\"" + max_time_str + "\"/>"
127                 + "</snoozed-notifications>";
128         TypedXmlPullParser parser = Xml.newFastPullParser();
129         parser.setInput(new BufferedInputStream(
130                 new ByteArrayInputStream(xml_string.getBytes())), null);
131         mSnoozeHelper.readXml(parser, 1);
132         ByteArrayOutputStream baos = new ByteArrayOutputStream();
133         TypedXmlSerializer serializer = Xml.newFastSerializer();
134         serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
135         serializer.startDocument(null, true);
136         mSnoozeHelper.writeXml(serializer);
137         serializer.endDocument();
138         serializer.flush();
139     }
140 
141     @Test
testWriteXMLformattedCorrectly_testCorrectContextURI()142     public void testWriteXMLformattedCorrectly_testCorrectContextURI()
143             throws XmlPullParserException, IOException {
144         final String max_time_str = Long.toString(Long.MAX_VALUE);
145         final String xml_string = "<snoozed-notifications>"
146                 + "<context version=\"1\" user-id=\"0\" notification=\"notification\" "
147                 + "pkg=\"pkg\" key=\"key\" id=\"uri\"/>"
148                 + "<context version=\"1\" user-id=\"0\" notification=\"notification\" "
149                 + "pkg=\"pkg\" key=\"key2\" id=\"uri\"/>"
150                 + "</snoozed-notifications>";
151         TypedXmlPullParser parser = Xml.newFastPullParser();
152         parser.setInput(new BufferedInputStream(
153                 new ByteArrayInputStream(xml_string.getBytes())), null);
154         mSnoozeHelper.readXml(parser, 1);
155         assertEquals("Should read the notification context from xml and it should be `uri",
156                 "uri", mSnoozeHelper.getSnoozeContextForUnpostedNotification(
157                         0, "pkg", "key"));
158     }
159 
160     @Test
testReadValidSnoozedFromCorrectly_timeDeadline()161     public void testReadValidSnoozedFromCorrectly_timeDeadline()
162             throws XmlPullParserException, IOException {
163         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
164         mSnoozeHelper.snooze(r, 999999999);
165         TypedXmlSerializer serializer = Xml.newFastSerializer();
166         ByteArrayOutputStream baos = new ByteArrayOutputStream();
167         serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
168         serializer.startDocument(null, true);
169         mSnoozeHelper.writeXml(serializer);
170         serializer.endDocument();
171         serializer.flush();
172 
173         TypedXmlPullParser parser = Xml.newFastPullParser();
174         parser.setInput(new BufferedInputStream(
175                 new ByteArrayInputStream(baos.toByteArray())), "utf-8");
176         mSnoozeHelper.readXml(parser, 1);
177         assertTrue("Should read the notification time from xml and it should be more than zero",
178                 0 < mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
179                         0, "pkg", r.getKey()).doubleValue());
180     }
181 
182 
183     @Test
testReadExpiredSnoozedNotification()184     public void testReadExpiredSnoozedNotification() throws
185             XmlPullParserException, IOException, InterruptedException {
186         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
187         mSnoozeHelper.snooze(r, 0);
188        // Thread.sleep(100);
189         TypedXmlSerializer serializer = Xml.newFastSerializer();
190         ByteArrayOutputStream baos = new ByteArrayOutputStream();
191         serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
192         serializer.startDocument(null, true);
193         mSnoozeHelper.writeXml(serializer);
194         serializer.endDocument();
195         serializer.flush();
196         Thread.sleep(10);
197         TypedXmlPullParser parser = Xml.newFastPullParser();
198         parser.setInput(new BufferedInputStream(
199                 new ByteArrayInputStream(baos.toByteArray())), "utf-8");
200         mSnoozeHelper.readXml(parser, 2);
201         int systemUser = UserHandle.SYSTEM.getIdentifier();
202         assertTrue("Should see a past time returned",
203                 System.currentTimeMillis() >  mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
204                         systemUser, "pkg", r.getKey()).longValue());
205     }
206 
207     @Test
testReadNoneSnoozedNotification()208     public void testReadNoneSnoozedNotification() throws XmlPullParserException,
209             IOException, InterruptedException {
210         NotificationRecord r = getNotificationRecord(
211                 "pkg", 1, "one", UserHandle.SYSTEM);
212         mSnoozeHelper.snooze(r, 0);
213 
214         assertEquals("should see a zero value for unsnoozed notification",
215                 0L,
216                 mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
217                         UserHandle.SYSTEM.getIdentifier(), "not_my_package",
218                         getNotificationRecord("not_my_package", 1, "one",
219                                 UserHandle.SYSTEM).getKey()).longValue());
220     }
221 
222     @Test
testScheduleRepostsForPersistedNotifications()223     public void testScheduleRepostsForPersistedNotifications() throws Exception {
224         final String xml_string = "<snoozed-notifications>"
225                 + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
226                 + "pkg=\"pkg\" key=\"key\" time=\"" + 10 + "\"/>"
227                 + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
228                 + "pkg=\"pkg\" key=\"key2\" time=\"" + 15+ "\"/>"
229                 + "</snoozed-notifications>";
230         TypedXmlPullParser parser = Xml.newFastPullParser();
231         parser.setInput(new BufferedInputStream(
232                 new ByteArrayInputStream(xml_string.getBytes())), null);
233         mSnoozeHelper.readXml(parser, 4);
234 
235         mSnoozeHelper.scheduleRepostsForPersistedNotifications(5);
236 
237         ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
238         verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq((long) 10), captor.capture());
239         assertEquals("key", captor.getValue().getIntent().getStringExtra(EXTRA_KEY));
240 
241         ArgumentCaptor<PendingIntent> captor2 = ArgumentCaptor.forClass(PendingIntent.class);
242         verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq((long) 15), captor2.capture());
243         assertEquals("key2", captor2.getValue().getIntent().getStringExtra(EXTRA_KEY));
244     }
245 
246     @Test
testLongTagPersistedNotification()247     public void testLongTagPersistedNotification() throws Exception {
248         String longTag = "A".repeat(66000);
249         NotificationRecord r = getNotificationRecord("pkg", 1, longTag, UserHandle.SYSTEM);
250         mSnoozeHelper.snooze(r, 0);
251 
252         // We store the full key in temp storage.
253         ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
254         verify(mAm).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
255         assertEquals(66010, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
256 
257         TypedXmlSerializer serializer = Xml.newFastSerializer();
258         ByteArrayOutputStream baos = new ByteArrayOutputStream();
259         serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
260         serializer.startDocument(null, true);
261         mSnoozeHelper.writeXml(serializer);
262         serializer.endDocument();
263         serializer.flush();
264 
265         TypedXmlPullParser parser = Xml.newFastPullParser();
266         parser.setInput(new BufferedInputStream(
267                 new ByteArrayInputStream(baos.toByteArray())), "utf-8");
268         mSnoozeHelper.readXml(parser, 4);
269 
270         mSnoozeHelper.scheduleRepostsForPersistedNotifications(5);
271 
272         // We trim the key in persistent storage.
273         verify(mAm, times(2)).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
274         assertEquals(1000, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
275     }
276 
277     @Test
testSnoozeForTime()278     public void testSnoozeForTime() throws Exception {
279         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
280         mSnoozeHelper.snooze(r, 1000);
281         ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
282         verify(mAm, times(1)).setExactAndAllowWhileIdle(
283                 anyInt(), captor.capture(), any(PendingIntent.class));
284         long actualSnoozedUntilDuration = captor.getValue() - System.currentTimeMillis();
285         assertTrue(Math.abs(actualSnoozedUntilDuration - 1000) < 250);
286         assertTrue(mSnoozeHelper.isSnoozed(
287                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
288     }
289 
290     @Test
291     public void testSnoozeSentToAndroid() throws Exception {
292         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
293         mSnoozeHelper.snooze(r, 1000);
294         ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
295         verify(mAm, times(1)).setExactAndAllowWhileIdle(
296                 anyInt(), anyLong(), captor.capture());
297         assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
298                 captor.getValue().getIntent().getPackage());
299     }
300 
301     @Test
302     public void testSnooze() throws Exception {
303         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
304         mSnoozeHelper.snooze(r, (String) null);
305         verify(mAm, never()).setExactAndAllowWhileIdle(
306                 anyInt(), anyLong(), any(PendingIntent.class));
307         assertTrue(mSnoozeHelper.isSnoozed(
308                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
309     }
310 
311     @Test
312     public void testSnoozeLimit() {
313         for (int i = 0; i < CONCURRENT_SNOOZE_LIMIT; i++ ) {
314             NotificationRecord r = getNotificationRecord("pkg", i, i+"", UserHandle.SYSTEM);
315 
316             assertTrue("cannot snooze record " + i, mSnoozeHelper.canSnooze(1));
317 
318             if (i % 2 == 0) {
319                 mSnoozeHelper.snooze(r, null);
320             } else {
321                 mSnoozeHelper.snooze(r, 9000);
322             }
323         }
324         assertFalse(mSnoozeHelper.canSnooze(1));
325     }
326 
327     @Test
328     public void testSnoozeLimit_maximumPersisted() throws XmlPullParserException, IOException {
329         final long snoozeTimeout = 1234;
330         final String snoozeContext = "ctx";
331         // Serialize & deserialize notifications so that only persisted lists are used
332         TypedXmlSerializer serializer = Xml.newFastSerializer();
333         ByteArrayOutputStream baos = new ByteArrayOutputStream();
334         serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
335         serializer.startDocument(null, true);
336         serializer.startTag(null, XML_TAG_NAME);
337         // Serialize maximum number of timed + context snoozed notifications, half of each
338         for (int i = 0; i < CONCURRENT_SNOOZE_LIMIT; i++) {
339             final boolean timedNotification = i % 2 == 0;
340             if (timedNotification) {
341                 serializer.startTag(null, XML_SNOOZED_NOTIFICATION);
342             } else {
343                 serializer.startTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT);
344             }
345             serializer.attributeInt(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, 1);
346             serializer.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, "key" + i);
347             if (timedNotification) {
348                 serializer.attributeLong(null, XML_SNOOZED_NOTIFICATION_TIME, snoozeTimeout);
349                 serializer.endTag(null, XML_SNOOZED_NOTIFICATION);
350             } else {
351                 serializer.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID, snoozeContext);
352                 serializer.endTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT);
353             }
354         }
355         serializer.endTag(null, XML_TAG_NAME);
356         serializer.endDocument();
357         serializer.flush();
358 
359         TypedXmlPullParser parser = Xml.newFastPullParser();
360         parser.setInput(new BufferedInputStream(
361                 new ByteArrayInputStream(baos.toByteArray())), "utf-8");
362         mSnoozeHelper.readXml(parser, 1);
363         // Verify that we can't snooze any more notifications
364         //  and that the limit is caused by persisted notifications
365         assertThat(mSnoozeHelper.canSnooze(1)).isFalse();
366         assertThat(mSnoozeHelper.isSnoozed(UserHandle.USER_SYSTEM, "pkg", "key0")).isFalse();
367         assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM,
368                 "pkg", "key0")).isEqualTo(snoozeTimeout);
369         assertThat(
370             mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
371                 "key1")).isEqualTo(snoozeContext);
372     }
373 
374     @Test
375     public void testCancelByApp() throws Exception {
376         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
377         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
378         mSnoozeHelper.snooze(r, 1000);
379         mSnoozeHelper.snooze(r2 , 1000);
380         assertTrue(mSnoozeHelper.isSnoozed(
381                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
382         assertTrue(mSnoozeHelper.isSnoozed(
383                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
384 
385         mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), "one", 1);
386         // 2 = one for each snooze, above, zero for the cancel.
387         verify(mAm, times(2)).cancel(any(PendingIntent.class));
388         assertTrue(mSnoozeHelper.isSnoozed(
389                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
390         assertTrue(mSnoozeHelper.isSnoozed(
391                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
392     }
393 
394     @Test
395     public void testCancelAllForUser() throws Exception {
396         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
397         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
398         NotificationRecord r3 = getNotificationRecord("pkg", 3, "three", UserHandle.ALL);
399         mSnoozeHelper.snooze(r,  1000);
400         mSnoozeHelper.snooze(r2, 1000);
401         mSnoozeHelper.snooze(r3, 1000);
402         assertTrue(mSnoozeHelper.isSnoozed(
403                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
404         assertTrue(mSnoozeHelper.isSnoozed(
405                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
406         assertTrue(mSnoozeHelper.isSnoozed(
407                 UserHandle.USER_ALL, r3.getSbn().getPackageName(), r3.getKey()));
408 
409         mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, false);
410         // 3 = once for each snooze above (3), only.
411         verify(mAm, times(3)).cancel(any(PendingIntent.class));
412         assertTrue(mSnoozeHelper.isSnoozed(
413                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
414         assertTrue(mSnoozeHelper.isSnoozed(
415                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
416         assertTrue(mSnoozeHelper.isSnoozed(
417                 UserHandle.USER_ALL, r3.getSbn().getPackageName(), r3.getKey()));
418     }
419 
420     @Test
421     public void testCancelAllByApp() throws Exception {
422         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
423         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
424         NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
425         mSnoozeHelper.snooze(r, 1000);
426         mSnoozeHelper.snooze(r2, 1000);
427         mSnoozeHelper.snooze(r3, 1000);
428         assertTrue(mSnoozeHelper.isSnoozed(
429                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
430         assertTrue(mSnoozeHelper.isSnoozed(
431                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
432         assertTrue(mSnoozeHelper.isSnoozed(
433                 UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
434 
435         mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, "pkg2");
436         // 3 = once for each snooze above (3), only.
437         verify(mAm, times(3)).cancel(any(PendingIntent.class));
438         assertTrue(mSnoozeHelper.isSnoozed(
439                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
440         assertTrue(mSnoozeHelper.isSnoozed(
441                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
442         assertTrue(mSnoozeHelper.isSnoozed(
443                 UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
444     }
445 
446     @Test
447     public void testCancelDoesNotUnsnooze() throws Exception {
448         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
449         mSnoozeHelper.snooze(r, 1000);
450         assertTrue(mSnoozeHelper.isSnoozed(
451                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
452 
453         mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), "one", 1);
454 
455         assertTrue(mSnoozeHelper.isSnoozed(
456                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
457     }
458 
459     @Test
460     public void testCancelDoesNotRepost() throws Exception {
461         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
462         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
463         mSnoozeHelper.snooze(r, 1000);
464         mSnoozeHelper.snooze(r2 , 1000);
465         assertTrue(mSnoozeHelper.isSnoozed(
466                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
467         assertTrue(mSnoozeHelper.isSnoozed(
468                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
469 
470         mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), "one", 1);
471 
472         mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM, false);
473         verify(mCallback, never()).repost(UserHandle.USER_SYSTEM, r, false);
474     }
475 
476     @Test
477     public void testRepost() throws Exception {
478         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
479         mSnoozeHelper.snooze(r, 1000);
480         NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL);
481         mSnoozeHelper.snooze(r2, 1000);
482         reset(mAm);
483         mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM, false);
484         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false);
485         ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
486         verify(mAm).cancel(captor.capture());
487         assertEquals(r.getKey(), captor.getValue().getIntent().getStringExtra(EXTRA_KEY));
488     }
489 
490     @Test
491     public void testRepost_noUser() throws Exception {
492         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
493         mSnoozeHelper.snooze(r, 1000);
494         NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL);
495         mSnoozeHelper.snooze(r2, 1000);
496         reset(mAm);
497         mSnoozeHelper.repost(r.getKey(), false);
498         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false);
499         verify(mAm).cancel(any(PendingIntent.class));
500     }
501 
502     @Test
503     public void testUpdate() throws Exception {
504         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
505         mSnoozeHelper.snooze(r , 1000);
506         r.getNotification().category = "NEW CATEGORY";
507 
508         mSnoozeHelper.update(UserHandle.USER_SYSTEM, r);
509         verify(mCallback, never()).repost(anyInt(), any(NotificationRecord.class), anyBoolean());
510 
511         mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM, false);
512         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false);
513     }
514 
515     @Test
516     public void testUpdateAfterCancel() throws Exception {
517         // snooze a notification
518         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
519         mSnoozeHelper.snooze(r , 1000);
520 
521         // cancel the notification
522         mSnoozeHelper.cancel(UserHandle.USER_SYSTEM, false);
523 
524         // update the notification
525         r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
526         mSnoozeHelper.update(UserHandle.USER_SYSTEM, r);
527 
528         // verify callback is called when repost (snooze is expired)
529         verify(mCallback, never()).repost(anyInt(), any(NotificationRecord.class), anyBoolean());
530         mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM, false);
531         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false);
532         assertFalse(r.isCanceled);
533     }
534 
535     @Test
536     public void testReport_passesFlag() throws Exception {
537         // snooze a notification
538         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
539         mSnoozeHelper.snooze(r , 1000);
540 
541         mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM, true);
542         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, true);
543     }
544 
545     @Test
546     public void testGetSnoozedBy() throws Exception {
547         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
548         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
549         NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
550         NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT);
551         mSnoozeHelper.snooze(r, 1000);
552         mSnoozeHelper.snooze(r2, 1000);
553         mSnoozeHelper.snooze(r3, 1000);
554         mSnoozeHelper.snooze(r4, 1000);
555         assertEquals(4, mSnoozeHelper.getSnoozed().size());
556     }
557 
558     @Test
559     public void testGetSnoozedGroupNotifications() throws Exception {
560         IntArray profileIds = new IntArray();
561         profileIds.add(UserHandle.USER_CURRENT);
562         when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
563         NotificationRecord r = getNotificationRecord("pkg", 1, "tag",
564                 UserHandle.CURRENT, "group", true);
565         NotificationRecord r2 = getNotificationRecord("pkg", 2, "tag",
566                 UserHandle.CURRENT, "group", true);
567         NotificationRecord r3 = getNotificationRecord("pkg2", 3, "tag",
568                 UserHandle.CURRENT, "group", true);
569         NotificationRecord r4 = getNotificationRecord("pkg2", 4, "tag",
570                 UserHandle.CURRENT, "group", true);
571         mSnoozeHelper.snooze(r, 1000);
572         mSnoozeHelper.snooze(r2, 1000);
573         mSnoozeHelper.snooze(r3, 1000);
574         mSnoozeHelper.snooze(r4, 1000);
575 
576         assertEquals(2,
577                 mSnoozeHelper.getNotifications("pkg", "group", UserHandle.USER_CURRENT).size());
578     }
579 
580     @Test
581     public void testGetSnoozedGroupNotifications_nonGrouped() throws Exception {
582         IntArray profileIds = new IntArray();
583         profileIds.add(UserHandle.USER_CURRENT);
584         when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
585         NotificationRecord r = getNotificationRecord("pkg", 1, "tag",
586                 UserHandle.CURRENT, "group", true);
587         NotificationRecord r2 = getNotificationRecord("pkg", 2, "tag",
588                 UserHandle.CURRENT, null, true);
589         mSnoozeHelper.snooze(r, 1000);
590         mSnoozeHelper.snooze(r2, 1000);
591 
592         assertEquals(1,
593                 mSnoozeHelper.getNotifications("pkg", "group", UserHandle.USER_CURRENT).size());
594         // and no NPE
595     }
596 
597     @Test
598     public void testGetSnoozedNotificationByKey() throws Exception {
599         IntArray profileIds = new IntArray();
600         profileIds.add(UserHandle.USER_CURRENT);
601         when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
602         NotificationRecord r = getNotificationRecord("pkg", 1, "tag",
603                 UserHandle.CURRENT, "group", true);
604         NotificationRecord r2 = getNotificationRecord("pkg", 2, "tag",
605                 UserHandle.CURRENT, "group", true);
606         NotificationRecord r3 = getNotificationRecord("pkg2", 3, "tag",
607                 UserHandle.CURRENT, "group", true);
608         NotificationRecord r4 = getNotificationRecord("pkg2", 4, "tag",
609                 UserHandle.CURRENT, "group", true);
610         mSnoozeHelper.snooze(r, 1000);
611         mSnoozeHelper.snooze(r2, 1000);
612         mSnoozeHelper.snooze(r3, 1000);
613         mSnoozeHelper.snooze(r4, 1000);
614 
615         assertEquals(r, mSnoozeHelper.getNotification(r.getKey()));
616     }
617 
618     @Test
619     public void testGetUnSnoozedNotificationByKey() throws Exception {
620         IntArray profileIds = new IntArray();
621         profileIds.add(UserHandle.USER_CURRENT);
622         when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
623         NotificationRecord r = getNotificationRecord("pkg", 1, "tag",
624                 UserHandle.CURRENT, "group", true);
625         NotificationRecord r2 = getNotificationRecord("pkg", 2, "tag",
626                 UserHandle.CURRENT, "group", true);
627         mSnoozeHelper.snooze(r2, 1000);
628 
629         assertEquals(null, mSnoozeHelper.getNotification(r.getKey()));
630     }
631 
632     @Test
633     public void repostGroupSummary_onlyFellowGroupChildren() throws Exception {
634         NotificationRecord r = getNotificationRecord(
635                 "pkg", 1, "one", UserHandle.SYSTEM, "group1", false);
636         NotificationRecord r2 = getNotificationRecord(
637                 "pkg", 2, "two", UserHandle.SYSTEM, "group1", false);
638         mSnoozeHelper.snooze(r, 1000);
639         mSnoozeHelper.snooze(r2, 1000);
640         mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, "group1");
641 
642         verify(mCallback, never()).repost(eq(UserHandle.USER_SYSTEM), eq(r), anyBoolean());
643     }
644 
645     @Test
646     public void repostGroupSummary_repostsSummary() throws Exception {
647         final int snoozeDuration = 1000;
648         IntArray profileIds = new IntArray();
649         profileIds.add(UserHandle.USER_SYSTEM);
650         when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
651         NotificationRecord r = getNotificationRecord(
652                 "pkg", 1, "one", UserHandle.SYSTEM, "group1", true);
653         NotificationRecord r2 = getNotificationRecord(
654                 "pkg", 2, "two", UserHandle.SYSTEM, "group1", false);
655         final long snoozeTime = System.currentTimeMillis() + snoozeDuration;
656         mSnoozeHelper.snooze(r, snoozeDuration);
657         mSnoozeHelper.snooze(r2, snoozeDuration);
658         assertEquals(2, mSnoozeHelper.getSnoozed().size());
659         assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
660         // Verify that summary notification was added to the persisted list
661         assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
662                 r.getKey())).isAtLeast(snoozeTime);
663 
664         mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey());
665 
666         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false);
667         verify(mCallback, never()).repost(UserHandle.USER_SYSTEM, r2, false);
668 
669         assertEquals(1, mSnoozeHelper.getSnoozed().size());
670         assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
671         // Verify that summary notification was removed from the persisted list
672         assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
673                 r.getKey())).isEqualTo(0);
674     }
675 
676     @Test
677     public void snoozeWithContext_repostGroupSummary_removesPersisted() throws Exception {
678         final String snoozeContext = "zzzzz";
679         IntArray profileIds = new IntArray();
680         profileIds.add(UserHandle.USER_SYSTEM);
681         when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
682         NotificationRecord r = getNotificationRecord(
683                 "pkg", 1, "one", UserHandle.SYSTEM, "group1", true);
684         NotificationRecord r2 = getNotificationRecord(
685                 "pkg", 2, "two", UserHandle.SYSTEM, "group1", false);
686         mSnoozeHelper.snooze(r, snoozeContext);
687         mSnoozeHelper.snooze(r2, snoozeContext);
688         assertEquals(2, mSnoozeHelper.getSnoozed().size());
689         assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
690         // Verify that summary notification was added to the persisted list
691         assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM,
692             "pkg", r.getKey())).isEqualTo(snoozeContext);
693 
694         mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey());
695 
696         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false);
697         verify(mCallback, never()).repost(UserHandle.USER_SYSTEM, r2, false);
698 
699         assertEquals(1, mSnoozeHelper.getSnoozed().size());
700         assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
701         // Verify that summary notification was removed from the persisted list
702         assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM,
703                 "pkg", r.getKey())).isNull();
704     }
705 
706     @Test
707     public void testClearData_userPackage() {
708         // snooze 2 from same package
709         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
710         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two" + "2".repeat(66000),
711                 UserHandle.SYSTEM); // include notif with very long tag
712         mSnoozeHelper.snooze(r, 1000);
713         mSnoozeHelper.snooze(r2, 1000);
714         assertTrue(mSnoozeHelper.isSnoozed(
715                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
716         assertTrue(mSnoozeHelper.isSnoozed(
717                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
718 
719         // clear data
720         mSnoozeHelper.clearData(UserHandle.USER_SYSTEM, "pkg");
721 
722         // nothing snoozed; alarms canceled
723         assertFalse(mSnoozeHelper.isSnoozed(
724                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
725         assertFalse(mSnoozeHelper.isSnoozed(
726                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
727         // twice for initial snooze, twice for canceling the snooze
728         verify(mAm, times(4)).cancel(any(PendingIntent.class));
729     }
730 
731     @Test
732     public void testClearData_user() {
733         // snooze 2 from same package, including notifs with long tag
734         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
735         NotificationRecord r2 = getNotificationRecord("pkg2", 2, "two" + "2".repeat(66000),
736                 UserHandle.SYSTEM);
737         NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three",
738                 UserHandle.SYSTEM);
739         NotificationRecord r4 = getNotificationRecord("pkg", 2, "two" + "2".repeat(66000),
740                 UserHandle.ALL);
741         mSnoozeHelper.snooze(r, 1000);
742         mSnoozeHelper.snooze(r2, 1000);
743         mSnoozeHelper.snooze(r3, "until");
744         mSnoozeHelper.snooze(r4, "until");
745 
746         assertTrue(mSnoozeHelper.isSnoozed(
747                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
748         assertTrue(mSnoozeHelper.isSnoozed(
749                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
750         assertTrue(mSnoozeHelper.isSnoozed(
751                 UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
752         assertTrue(mSnoozeHelper.isSnoozed(
753                 UserHandle.USER_ALL, r4.getSbn().getPackageName(), r4.getKey()));
754 
755         assertFalse(0L == mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
756                 r.getUser().getIdentifier(), r.getSbn().getPackageName(),
757                 r.getSbn().getKey()));
758         assertFalse(0L == mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
759                 r2.getUser().getIdentifier(), r2.getSbn().getPackageName(),
760                 r2.getSbn().getKey()));
761         assertNotNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
762                 r3.getUser().getIdentifier(), r3.getSbn().getPackageName(),
763                 r3.getSbn().getKey()));
764         assertNotNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
765                 r4.getUser().getIdentifier(), r4.getSbn().getPackageName(),
766                 r4.getSbn().getKey()));
767 
768         // clear data
769         mSnoozeHelper.clearData(UserHandle.USER_SYSTEM);
770 
771         // nothing in USER_SYSTEM snoozed; alarms canceled
772         assertFalse(mSnoozeHelper.isSnoozed(
773                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
774         assertFalse(mSnoozeHelper.isSnoozed(
775                 UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
776         assertFalse(mSnoozeHelper.isSnoozed(
777                 UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
778         assertTrue(mSnoozeHelper.isSnoozed(
779                 UserHandle.USER_SYSTEM, r4.getSbn().getPackageName(), r4.getKey()));
780 
781         assertEquals(0L, mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
782                 r.getUser().getIdentifier(), r.getSbn().getPackageName(),
783                 r.getSbn().getKey()).longValue());
784         assertEquals(0L, mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
785                 r2.getUser().getIdentifier(), r2.getSbn().getPackageName(),
786                 r2.getSbn().getKey()).longValue());
787         assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
788                 r3.getUser().getIdentifier(), r3.getSbn().getPackageName(),
789                 r3.getSbn().getKey()));
790         assertNotNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
791                 r4.getUser().getIdentifier(), r4.getSbn().getPackageName(),
792                 r4.getSbn().getKey()));
793 
794         // 2 for initial timed-snoozes, once each for canceling the USER_SYSTEM snoozes
795         verify(mAm, times(5)).cancel(any(PendingIntent.class));
796     }
797 
798     @Test
799     public void testClearData_otherRecordsUntouched() {
800         // 2 packages, 2 users
801         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
802         NotificationRecord rb = getNotificationRecord("pkg", 1, "oneb", UserHandle.SYSTEM);
803         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.ALL);
804         NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
805         mSnoozeHelper.snooze(r, 1000);
806         mSnoozeHelper.snooze(rb, "until");
807         mSnoozeHelper.snooze(r2, 1000);
808         mSnoozeHelper.snooze(r3, 1000);
809         assertTrue(mSnoozeHelper.isSnoozed(
810                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
811         assertTrue(mSnoozeHelper.isSnoozed(
812                 UserHandle.USER_SYSTEM, rb.getSbn().getPackageName(), rb.getKey()));
813         assertTrue(mSnoozeHelper.isSnoozed(
814                 UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey()));
815         assertTrue(mSnoozeHelper.isSnoozed(
816                 UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
817 
818         // clear data
819         mSnoozeHelper.clearData(UserHandle.USER_SYSTEM, "pkg");
820 
821         assertFalse(mSnoozeHelper.isSnoozed(
822                 UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
823         assertFalse(mSnoozeHelper.isSnoozed(
824                 UserHandle.USER_SYSTEM, rb.getSbn().getPackageName(), rb.getKey()));
825         assertTrue(mSnoozeHelper.isSnoozed(
826                 UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey()));
827         assertTrue(mSnoozeHelper.isSnoozed(
828                 UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
829 
830         assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
831                 rb.getUser().getIdentifier(), rb.getSbn().getPackageName(),
832                 rb.getSbn().getKey()));
833         assertEquals(0L, mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
834                 r.getUser().getIdentifier(), r.getSbn().getPackageName(),
835                 r.getSbn().getKey()).longValue());
836 
837         // once for each initial snooze, once for canceling one snooze
838         verify(mAm, times(5)).cancel(any(PendingIntent.class));
839     }
840 
841     private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
842             UserHandle user, String groupKey, boolean groupSummary) {
843         Notification n = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
844                 .setContentTitle("A")
845                 .setGroup("G")
846                 .setSortKey("A")
847                 .setWhen(1205)
848                 .setGroup(groupKey)
849                 .setGroupSummary(groupSummary)
850                 .build();
851         final NotificationChannel notificationChannel = new NotificationChannel(
852                 TEST_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_LOW);
853         return new NotificationRecord(getContext(), new StatusBarNotification(
854                 pkg, pkg, id, tag, 0, 0, n, user, null,
855                 System.currentTimeMillis()), notificationChannel);
856     }
857 
858     private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
859             UserHandle user) {
860         return getNotificationRecord(pkg, id, tag, user, null, false);
861     }
862 }
863