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