1 /* 2 * Copyright (C) 2022 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 android.app.notification.current.cts; 18 19 import static android.Manifest.permission.POST_NOTIFICATIONS; 20 import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL; 21 import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS; 22 import static android.app.Notification.FLAG_BUBBLE; 23 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; 24 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; 25 import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED; 26 import static android.app.stubs.BubbledActivity.EXTRA_LOCUS_ID; 27 import static android.app.stubs.BubblesTestService.EXTRA_TEST_CASE; 28 import static android.app.stubs.BubblesTestService.TEST_CALL; 29 import static android.app.stubs.BubblesTestService.TEST_MESSAGING; 30 import static android.app.stubs.SendBubbleActivity.BUBBLE_NOTIF_ID; 31 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; 32 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; 33 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 34 35 import static org.junit.Assert.assertEquals; 36 import static org.junit.Assert.assertFalse; 37 import static org.junit.Assert.assertNotNull; 38 import static org.junit.Assert.assertNull; 39 import static org.junit.Assert.assertThrows; 40 import static org.junit.Assert.assertTrue; 41 42 import android.app.Activity; 43 import android.app.ActivityOptions; 44 import android.app.Instrumentation; 45 import android.app.KeyguardManager; 46 import android.app.Notification; 47 import android.app.PendingIntent; 48 import android.app.stubs.BubbledActivity; 49 import android.app.stubs.BubblesTestService; 50 import android.app.stubs.R; 51 import android.app.stubs.SendBubbleActivity; 52 import android.app.stubs.shared.NotificationHelper.SEARCH_TYPE; 53 import android.content.BroadcastReceiver; 54 import android.content.Context; 55 import android.content.Intent; 56 import android.content.IntentFilter; 57 import android.content.LocusId; 58 import android.content.res.Resources; 59 import android.graphics.drawable.Icon; 60 import android.os.Bundle; 61 import android.permission.PermissionManager; 62 import android.permission.cts.PermissionUtils; 63 import android.provider.Settings; 64 import android.service.notification.NotificationListenerService; 65 import android.service.notification.StatusBarNotification; 66 67 import androidx.test.platform.app.InstrumentationRegistry; 68 import androidx.test.runner.AndroidJUnit4; 69 70 import com.android.compatibility.common.util.FeatureUtil; 71 import com.android.compatibility.common.util.SystemUtil; 72 73 import org.junit.After; 74 import org.junit.Before; 75 import org.junit.Test; 76 import org.junit.runner.RunWith; 77 78 import java.util.concurrent.CountDownLatch; 79 import java.util.concurrent.TimeUnit; 80 81 /** 82 * Tests bubbles related logic in NotificationManager. 83 */ 84 @RunWith(AndroidJUnit4.class) 85 public class NotificationManagerBubbleTest extends BaseNotificationManagerTest { 86 87 private static final String TAG = NotificationManagerBubbleTest.class.getSimpleName(); 88 89 private static final String STUB_PACKAGE_NAME = "android.app.stubs"; 90 91 // use a value of 10000 for consistency with other CTS tests (see 92 // android.server.wm.intentLaunchRunner#ACTIVITY_LAUNCH_TIMEOUT) 93 private static final int ACTIVITY_LAUNCH_TIMEOUT = 10000; 94 95 private BroadcastReceiver mBubbleBroadcastReceiver; 96 private boolean mBubblesEnabledSettingToRestore; 97 98 @Before setUp()99 public void setUp() throws Exception { 100 PermissionUtils.grantPermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS); 101 PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS); 102 103 // This setting is forced on / off for certain tests, save it & restore what's on the 104 // device after tests are run 105 mBubblesEnabledSettingToRestore = Settings.Secure.getInt(mContext.getContentResolver(), 106 Settings.Secure.NOTIFICATION_BUBBLES) == 1; 107 108 // ensure listener access isn't allowed before test runs (other tests could put 109 // TestListener in an unexpected state) 110 mNotificationHelper.disableListener(STUB_PACKAGE_NAME); 111 112 // delay between tests so notifications aren't dropped by the rate limiter 113 sleep(); 114 } 115 116 @After tearDown()117 public void tearDown() throws Exception { 118 119 // Restore bubbles setting 120 setBubblesGlobal(mBubblesEnabledSettingToRestore); 121 122 // Use test API to prevent PermissionManager from killing the test process when revoking 123 // permission. 124 SystemUtil.runWithShellPermissionIdentity( 125 () -> mContext.getSystemService(PermissionManager.class) 126 .revokePostNotificationPermissionWithoutKillForTest( 127 mContext.getPackageName(), 128 android.os.Process.myUserHandle().getIdentifier()), 129 REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL, 130 REVOKE_RUNTIME_PERMISSIONS); 131 PermissionUtils.revokePermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS); 132 } 133 isBubblesFeatureSupported()134 private boolean isBubblesFeatureSupported() { 135 // These do not support bubbles. 136 return (!mActivityManager.isLowRamDevice() && !FeatureUtil.isWatch() 137 && !FeatureUtil.isAutomotive() && !FeatureUtil.isTV() 138 && mContext.getResources().getBoolean(Resources.getSystem().getIdentifier( 139 "config_supportsBubble", "bool", "android"))); 140 } 141 sleep()142 private void sleep() { 143 try { 144 Thread.sleep(500); 145 } catch (InterruptedException ignored) { 146 } 147 } 148 sendAndVerifyBubble(final int id, Notification.Builder builder, boolean shouldBeBubble)149 private void sendAndVerifyBubble(final int id, Notification.Builder builder, 150 boolean shouldBeBubble) { 151 setUpNotifListener(); 152 153 Notification notif = builder.build(); 154 mNotificationManager.notify(id, notif); 155 156 verifyNotificationBubbleState(id, shouldBeBubble); 157 } 158 getDefaultNotifBuilder(int id)159 private Notification.Builder getDefaultNotifBuilder(int id) { 160 return new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) 161 .setSmallIcon(R.drawable.black) 162 .setWhen(System.currentTimeMillis()) 163 .setContentTitle("notify#" + id) 164 .setContentText("This is #" + id + "notification ") 165 .setContentIntent(getDefaultNotifPendingIntent()); 166 } 167 getDefaultNotifPendingIntent()168 private PendingIntent getDefaultNotifPendingIntent() { 169 final Intent intent = new Intent(mContext, BubbledActivity.class); 170 171 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP 172 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 173 intent.setAction(Intent.ACTION_MAIN); 174 return PendingIntent.getActivity(mContext, 0, intent, 175 PendingIntent.FLAG_MUTABLE_UNAUDITED); 176 } 177 getDefaultBubbleMetadata()178 private Notification.BubbleMetadata getDefaultBubbleMetadata() { 179 return new Notification.BubbleMetadata.Builder( 180 getDefaultNotifPendingIntent(), 181 Icon.createWithResource(mContext, R.drawable.black)) 182 .build(); 183 } 184 185 /** 186 * Make sure {@link #setUpNotifListener()} is called prior to sending the notif and verifying 187 * in this method. 188 */ verifyNotificationBubbleState(int id, boolean shouldBeBubble)189 private void verifyNotificationBubbleState(int id, boolean shouldBeBubble) { 190 StatusBarNotification sbn = 191 mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.POSTED); 192 assertNotNull("Cannot find notification id=" + id, sbn); 193 assertEquals("Unexpected bubble state for id=" + id + ", expected " + shouldBeBubble, 194 shouldBeBubble, ((sbn.getNotification().flags & FLAG_BUBBLE) != 0)); 195 } 196 setBubblesGlobal(boolean enabled)197 private void setBubblesGlobal(boolean enabled) { 198 SystemUtil.runWithShellPermissionIdentity(() -> 199 Settings.Secure.putInt(mContext.getContentResolver(), 200 Settings.Secure.NOTIFICATION_BUBBLES, enabled ? 1 : 0)); 201 } 202 setBubblesAppPref(int pref)203 private void setBubblesAppPref(int pref) throws Exception { 204 int userId = mContext.getUser().getIdentifier(); 205 String pkg = mContext.getPackageName(); 206 String command = " cmd notification set_bubbles " + pkg 207 + " " + Integer.toString(pref) 208 + " " + userId; 209 mNotificationHelper.runCommand(command, InstrumentationRegistry.getInstrumentation()); 210 } 211 setBubblesChannelAllowed(boolean allowed)212 private void setBubblesChannelAllowed(boolean allowed) throws Exception { 213 int userId = mContext.getUser().getIdentifier(); 214 String pkg = mContext.getPackageName(); 215 String command = " cmd notification set_bubbles_channel " + pkg 216 + " " + NOTIFICATION_CHANNEL_ID 217 + " " + allowed 218 + " " + userId; 219 mNotificationHelper.runCommand(command, InstrumentationRegistry.getInstrumentation()); 220 } 221 allowAllNotificationsToBubble()222 private void allowAllNotificationsToBubble() throws Exception { 223 setBubblesGlobal(true); 224 setBubblesAppPref(1 /* all */); 225 setBubblesChannelAllowed(true); 226 sleep(); // wait for ranking update 227 } 228 229 /** 230 * Starts an activity that is able to send a bubble; also handles unlocking the device. 231 * Any tests that use this method should be sure to call {@link #cleanupSendBubbleActivity()} 232 * to unregister the related broadcast receiver. 233 * 234 * @return the SendBubbleActivity that was opened. 235 */ startSendBubbleActivity()236 private SendBubbleActivity startSendBubbleActivity() throws Exception { 237 final CountDownLatch latch = new CountDownLatch(1); 238 mBubbleBroadcastReceiver = new BroadcastReceiver() { 239 @Override 240 public void onReceive(Context context, Intent intent) { 241 latch.countDown(); 242 } 243 }; 244 IntentFilter filter = new IntentFilter(SendBubbleActivity.BUBBLE_ACTIVITY_OPENED); 245 mContext.registerReceiver(mBubbleBroadcastReceiver, filter, Context.RECEIVER_EXPORTED); 246 247 // Start & get the activity 248 Class clazz = SendBubbleActivity.class; 249 250 Instrumentation.ActivityResult result = 251 new Instrumentation.ActivityResult(0, new Intent()); 252 Instrumentation.ActivityMonitor monitor = 253 new Instrumentation.ActivityMonitor(clazz.getName(), result, false); 254 InstrumentationRegistry.getInstrumentation().addMonitor(monitor); 255 256 Intent i = new Intent(mContext, SendBubbleActivity.class); 257 i.setFlags(FLAG_ACTIVITY_NEW_TASK); 258 InstrumentationRegistry.getInstrumentation().startActivitySync(i); 259 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 260 261 SendBubbleActivity sendBubbleActivity = (SendBubbleActivity) monitor.waitForActivity(); 262 263 // Make sure device is unlocked 264 ensureDeviceUnlocked(sendBubbleActivity); 265 latch.await(500, TimeUnit.MILLISECONDS); 266 267 return sendBubbleActivity; 268 } 269 startBubbledActivityMonitor()270 private Instrumentation.ActivityMonitor startBubbledActivityMonitor() { 271 Class<BubbledActivity> clazz = BubbledActivity.class; 272 Instrumentation.ActivityResult result = 273 new Instrumentation.ActivityResult(0, new Intent()); 274 Instrumentation.ActivityMonitor monitor = 275 new Instrumentation.ActivityMonitor(clazz.getName(), result, false); 276 InstrumentationRegistry.getInstrumentation().addMonitor(monitor); 277 return monitor; 278 } 279 startBubbleActivity(int id)280 private BubbledActivity startBubbleActivity(int id) { 281 return startBubbleActivity(id, true /* addLocusId */); 282 } 283 284 /** 285 * Starts the same activity that is in the bubble produced by this activity. 286 */ startBubbleActivity(int id, boolean addLocusId)287 private BubbledActivity startBubbleActivity(int id, boolean addLocusId) { 288 Instrumentation.ActivityMonitor monitor = startBubbledActivityMonitor(); 289 290 final Intent intent = new Intent(mContext, BubbledActivity.class); 291 // Clear any previous instance of this activity 292 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); 293 if (addLocusId) { 294 intent.putExtra(EXTRA_LOCUS_ID, String.valueOf(id)); 295 } 296 297 InstrumentationRegistry.getInstrumentation().startActivitySync(intent); 298 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 299 300 BubbledActivity bubbledActivity = (BubbledActivity) monitor.waitForActivity(); 301 ensureDeviceUnlocked(bubbledActivity); 302 return bubbledActivity; 303 } 304 305 /** 306 * Make sure device is unlocked so the activity can become visible 307 */ ensureDeviceUnlocked(Activity activity)308 private void ensureDeviceUnlocked(Activity activity) { 309 // Make sure device is unlocked 310 KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class); 311 if (keyguardManager.isKeyguardLocked()) { 312 CountDownLatch latch = new CountDownLatch(1); 313 keyguardManager.requestDismissKeyguard(activity, 314 new KeyguardManager.KeyguardDismissCallback() { 315 @Override 316 public void onDismissSucceeded() { 317 latch.countDown(); 318 } 319 }); 320 try { 321 latch.await(500, TimeUnit.MILLISECONDS); 322 } catch (InterruptedException ignored) { 323 } 324 } 325 } 326 cleanupSendBubbleActivity()327 private void cleanupSendBubbleActivity() { 328 mContext.unregisterReceiver(mBubbleBroadcastReceiver); 329 } 330 331 @Test testCanBubble_ranking()332 public void testCanBubble_ranking() throws Exception { 333 if (!isBubblesFeatureSupported()) { 334 return; 335 } 336 337 // turn on bubbles globally 338 setBubblesGlobal(true); 339 sleep(); 340 341 assertEquals(1, Settings.Secure.getInt( 342 mContext.getContentResolver(), Settings.Secure.NOTIFICATION_BUBBLES)); 343 344 mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME); 345 assertNotNull(mListener); 346 347 sendNotification(1, R.drawable.black); 348 // wait for notification listener to receive notification 349 mNotificationHelper.findPostedNotification(null, 1, SEARCH_TYPE.POSTED); 350 NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap; 351 NotificationListenerService.Ranking outRanking = 352 new NotificationListenerService.Ranking(); 353 for (String key : rankingMap.getOrderedKeys()) { 354 if (key.contains(mListener.getPackageName())) { 355 rankingMap.getRanking(key, outRanking); 356 // by default nothing can bubble 357 assertFalse(outRanking.canBubble()); 358 } 359 } 360 361 // turn off bubbles globally 362 setBubblesGlobal(false); 363 sleep(); 364 365 rankingMap = mListener.mRankingMap; 366 outRanking = new NotificationListenerService.Ranking(); 367 for (String key : rankingMap.getOrderedKeys()) { 368 if (key.contains(mListener.getPackageName())) { 369 rankingMap.getRanking(key, outRanking); 370 assertFalse(outRanking.canBubble()); 371 } 372 } 373 374 mListener.resetData(); 375 } 376 377 @Test testAreBubblesAllowed_appNone()378 public void testAreBubblesAllowed_appNone() throws Exception { 379 if (!isBubblesFeatureSupported()) { 380 return; 381 } 382 setBubblesAppPref(BUBBLE_PREFERENCE_NONE); 383 sleep(); 384 assertFalse(mNotificationManager.areBubblesAllowed()); 385 } 386 387 @Test testAreBubblesAllowed_appSelected()388 public void testAreBubblesAllowed_appSelected() throws Exception { 389 if (!isBubblesFeatureSupported()) { 390 return; 391 } 392 setBubblesAppPref(BUBBLE_PREFERENCE_SELECTED); 393 sleep(); 394 assertFalse(mNotificationManager.areBubblesAllowed()); 395 } 396 397 @Test testAreBubblesAllowed_appAll()398 public void testAreBubblesAllowed_appAll() throws Exception { 399 if (!isBubblesFeatureSupported()) { 400 return; 401 } 402 setBubblesAppPref(BUBBLE_PREFERENCE_ALL); 403 sleep(); 404 assertTrue(mNotificationManager.areBubblesAllowed()); 405 } 406 407 @Test testGetBubblePreference_appNone()408 public void testGetBubblePreference_appNone() throws Exception { 409 if (!isBubblesFeatureSupported()) { 410 return; 411 } 412 setBubblesAppPref(BUBBLE_PREFERENCE_NONE); 413 sleep(); 414 assertEquals(BUBBLE_PREFERENCE_NONE, mNotificationManager.getBubblePreference()); 415 } 416 417 @Test testGetBubblePreference_appSelected()418 public void testGetBubblePreference_appSelected() throws Exception { 419 if (!isBubblesFeatureSupported()) { 420 return; 421 } 422 setBubblesAppPref(BUBBLE_PREFERENCE_SELECTED); 423 sleep(); 424 assertEquals(BUBBLE_PREFERENCE_SELECTED, mNotificationManager.getBubblePreference()); 425 } 426 427 @Test testGetBubblePreference_appAll()428 public void testGetBubblePreference_appAll() throws Exception { 429 if (!isBubblesFeatureSupported()) { 430 return; 431 } 432 setBubblesAppPref(BUBBLE_PREFERENCE_ALL); 433 sleep(); 434 assertEquals(BUBBLE_PREFERENCE_ALL, mNotificationManager.getBubblePreference()); 435 } 436 437 @Test testAreBubblesEnabled()438 public void testAreBubblesEnabled() { 439 if (!isBubblesFeatureSupported()) { 440 return; 441 } 442 setBubblesGlobal(true); 443 sleep(); 444 assertTrue(mNotificationManager.areBubblesEnabled()); 445 } 446 447 @Test testAreBubblesEnabled_false()448 public void testAreBubblesEnabled_false() { 449 if (!isBubblesFeatureSupported()) { 450 return; 451 } 452 setBubblesGlobal(false); 453 sleep(); 454 assertFalse(mNotificationManager.areBubblesEnabled()); 455 } 456 457 @Test testNotificationManagerBubblePolicy_flag_intentBubble()458 public void testNotificationManagerBubblePolicy_flag_intentBubble() 459 throws Exception { 460 if (!isBubblesFeatureSupported()) { 461 return; 462 } 463 try { 464 allowAllNotificationsToBubble(); 465 createDynamicShortcut(); 466 467 Notification.Builder nb = getConversationNotification(); 468 nb.setBubbleMetadata(getDefaultBubbleMetadata()); 469 boolean shouldBeBubble = !mActivityManager.isLowRamDevice(); 470 sendAndVerifyBubble(1, nb, shouldBeBubble); 471 } finally { 472 deleteShortcuts(); 473 } 474 } 475 476 @Test testNotificationManagerBubblePolicy_noFlag_service()477 public void testNotificationManagerBubblePolicy_noFlag_service() 478 throws Exception { 479 if (!isBubblesFeatureSupported()) { 480 return; 481 } 482 Intent serviceIntent = new Intent(mContext, BubblesTestService.class); 483 serviceIntent.putExtra(EXTRA_TEST_CASE, TEST_MESSAGING); 484 try { 485 allowAllNotificationsToBubble(); 486 487 createDynamicShortcut(); 488 setUpNotifListener(); 489 490 mContext.startService(serviceIntent); 491 492 // No services in R (allowed in Q) 493 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, false /* shouldBeBubble */); 494 } finally { 495 deleteShortcuts(); 496 mContext.stopService(serviceIntent); 497 } 498 } 499 500 @Test testNotificationManagerBubblePolicy_noFlag_phonecall()501 public void testNotificationManagerBubblePolicy_noFlag_phonecall() 502 throws Exception { 503 if (!isBubblesFeatureSupported()) { 504 return; 505 } 506 Intent serviceIntent = new Intent(mContext, BubblesTestService.class); 507 serviceIntent.putExtra(EXTRA_TEST_CASE, TEST_CALL); 508 509 try { 510 allowAllNotificationsToBubble(); 511 512 createDynamicShortcut(); 513 setUpNotifListener(); 514 515 mContext.startService(serviceIntent); 516 517 // No phonecalls in R (allowed in Q) 518 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, false /* shouldBeBubble */); 519 } finally { 520 deleteShortcuts(); 521 mContext.stopService(serviceIntent); 522 } 523 } 524 525 @Test testNotificationManagerBubblePolicy_noFlag_foreground()526 public void testNotificationManagerBubblePolicy_noFlag_foreground() throws Exception { 527 if (!isBubblesFeatureSupported()) { 528 return; 529 } 530 try { 531 allowAllNotificationsToBubble(); 532 533 createDynamicShortcut(); 534 setUpNotifListener(); 535 536 // Start & get the activity 537 SendBubbleActivity a = startSendBubbleActivity(); 538 // Send a bubble that doesn't fulfill policy from foreground 539 a.sendInvalidBubble(BUBBLE_NOTIF_ID, false /* autoExpand */); 540 541 // No foreground bubbles that don't fulfill policy in R (allowed in Q) 542 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, false /* shouldBeBubble */); 543 } finally { 544 deleteShortcuts(); 545 cleanupSendBubbleActivity(); 546 } 547 } 548 549 @Test testNotificationManagerBubble_checkActivityFlagsDocumentLaunchMode()550 public void testNotificationManagerBubble_checkActivityFlagsDocumentLaunchMode() 551 throws Exception { 552 if (!isBubblesFeatureSupported()) { 553 return; 554 } 555 try { 556 allowAllNotificationsToBubble(); 557 558 createDynamicShortcut(); 559 setUpNotifListener(); 560 561 // make ourselves foreground so we can auto-expand the bubble & check the intent flags 562 SendBubbleActivity a = startSendBubbleActivity(); 563 564 // Prep to find bubbled activity 565 Instrumentation.ActivityMonitor monitor = startBubbledActivityMonitor(); 566 567 a.sendBubble(BUBBLE_NOTIF_ID, true /* autoExpand */, false /* suppressNotif */); 568 569 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, true /* shouldBeBubble */); 570 571 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 572 573 BubbledActivity activity = (BubbledActivity) monitor.waitForActivityWithTimeout( 574 ACTIVITY_LAUNCH_TIMEOUT); 575 assertNotNull(String.format( 576 "Failed to detect BubbleActivity after %d ms", ACTIVITY_LAUNCH_TIMEOUT), 577 activity); 578 assertTrue((activity.getIntent().getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT) != 0); 579 assertTrue((activity.getIntent().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0); 580 } finally { 581 deleteShortcuts(); 582 cleanupSendBubbleActivity(); 583 } 584 } 585 586 @Test testNotificationManagerBubblePolicy_flag_shortcutBubble()587 public void testNotificationManagerBubblePolicy_flag_shortcutBubble() 588 throws Exception { 589 if (!isBubblesFeatureSupported()) { 590 return; 591 } 592 try { 593 allowAllNotificationsToBubble(); 594 595 createDynamicShortcut(); 596 597 Notification.Builder nb = getConversationNotification(); 598 nb.setBubbleMetadata( 599 new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID).build()); 600 601 boolean shouldBeBubble = !mActivityManager.isLowRamDevice(); 602 sendAndVerifyBubble(1, nb, shouldBeBubble); 603 } finally { 604 deleteShortcuts(); 605 } 606 } 607 608 @Test testNotificationManagerBubblePolicy_noFlag_invalidShortcut()609 public void testNotificationManagerBubblePolicy_noFlag_invalidShortcut() 610 throws Exception { 611 if (!isBubblesFeatureSupported()) { 612 return; 613 } 614 try { 615 allowAllNotificationsToBubble(); 616 617 createDynamicShortcut(); 618 619 Notification.Builder nb = getConversationNotification(); 620 nb.setShortcutId("invalid"); 621 nb.setBubbleMetadata(new Notification.BubbleMetadata.Builder("invalid").build()); 622 623 sendAndVerifyBubble(1, nb, false); 624 } finally { 625 deleteShortcuts(); 626 } 627 } 628 629 @Test testNotificationManagerBubblePolicy_noFlag_invalidNotif()630 public void testNotificationManagerBubblePolicy_noFlag_invalidNotif() 631 throws Exception { 632 if (!isBubblesFeatureSupported()) { 633 return; 634 } 635 try { 636 allowAllNotificationsToBubble(); 637 638 createDynamicShortcut(); 639 640 int id = 1; 641 Notification.Builder nb = getDefaultNotifBuilder(id); 642 nb.setBubbleMetadata( 643 new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID).build()); 644 645 sendAndVerifyBubble(id, nb, false /* shouldBeBubble */); 646 } finally { 647 deleteShortcuts(); 648 } 649 } 650 651 @Test testNotificationManagerBubblePolicy_appAll_globalOn()652 public void testNotificationManagerBubblePolicy_appAll_globalOn() throws Exception { 653 if (!isBubblesFeatureSupported()) { 654 return; 655 } 656 try { 657 allowAllNotificationsToBubble(); 658 659 createDynamicShortcut(); 660 Notification.Builder nb = getConversationNotification(); 661 nb.setBubbleMetadata( 662 new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID).build()); 663 664 boolean shouldBeBubble = !mActivityManager.isLowRamDevice(); 665 sendAndVerifyBubble(1, nb, shouldBeBubble); 666 } finally { 667 deleteShortcuts(); 668 } 669 } 670 671 @Test testNotificationManagerBubblePolicy_appAll_globalOff()672 public void testNotificationManagerBubblePolicy_appAll_globalOff() throws Exception { 673 if (!isBubblesFeatureSupported()) { 674 return; 675 } 676 try { 677 setBubblesGlobal(false); 678 setBubblesAppPref(1 /* all */); 679 setBubblesChannelAllowed(true); 680 sleep(); 681 682 createDynamicShortcut(); 683 Notification.Builder nb = getConversationNotification(); 684 nb.setBubbleMetadata(new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID) 685 .build()); 686 687 sendAndVerifyBubble(1, nb, false); 688 } finally { 689 deleteShortcuts(); 690 } 691 } 692 693 @Test testNotificationManagerBubblePolicy_appAll_channelNo()694 public void testNotificationManagerBubblePolicy_appAll_channelNo() throws Exception { 695 if (!isBubblesFeatureSupported()) { 696 return; 697 } 698 try { 699 setBubblesGlobal(true); 700 setBubblesAppPref(1 /* all */); 701 setBubblesChannelAllowed(false); 702 sleep(); 703 704 createDynamicShortcut(); 705 Notification.Builder nb = getConversationNotification(); 706 nb.setBubbleMetadata(new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID) 707 .build()); 708 709 sendAndVerifyBubble(1, nb, false); 710 } finally { 711 deleteShortcuts(); 712 } 713 } 714 715 @Test testNotificationManagerBubblePolicy_appSelected_channelNo()716 public void testNotificationManagerBubblePolicy_appSelected_channelNo() throws Exception { 717 if (!isBubblesFeatureSupported()) { 718 return; 719 } 720 try { 721 setBubblesGlobal(true); 722 setBubblesAppPref(2 /* selected */); 723 setBubblesChannelAllowed(false); 724 sleep(); 725 726 createDynamicShortcut(); 727 Notification.Builder nb = getConversationNotification(); 728 nb.setBubbleMetadata(new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID) 729 .build()); 730 731 sendAndVerifyBubble(1, nb, false); 732 } finally { 733 deleteShortcuts(); 734 } 735 } 736 737 @Test testNotificationManagerBubblePolicy_appSelected_channelYes()738 public void testNotificationManagerBubblePolicy_appSelected_channelYes() throws Exception { 739 if (!isBubblesFeatureSupported()) { 740 return; 741 } 742 try { 743 setBubblesGlobal(true); 744 setBubblesAppPref(2 /* selected */); 745 setBubblesChannelAllowed(true); 746 sleep(); 747 748 createDynamicShortcut(); 749 Notification.Builder nb = getConversationNotification(); 750 nb.setBubbleMetadata(new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID) 751 .build()); 752 753 boolean shouldBeBubble = !mActivityManager.isLowRamDevice(); 754 sendAndVerifyBubble(1, nb, shouldBeBubble); 755 } finally { 756 deleteShortcuts(); 757 } 758 } 759 760 @Test testNotificationManagerBubblePolicy_appNone_channelNo()761 public void testNotificationManagerBubblePolicy_appNone_channelNo() throws Exception { 762 if (!isBubblesFeatureSupported()) { 763 return; 764 } 765 try { 766 setBubblesGlobal(true); 767 setBubblesAppPref(0 /* none */); 768 setBubblesChannelAllowed(false); 769 sleep(); 770 771 createDynamicShortcut(); 772 Notification.Builder nb = getConversationNotification(); 773 nb.setBubbleMetadata(new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID) 774 .build()); 775 776 sendAndVerifyBubble(1, nb, false); 777 } finally { 778 deleteShortcuts(); 779 } 780 } 781 782 @Test testNotificationManagerBubblePolicy_noFlag_shortcutRemoved()783 public void testNotificationManagerBubblePolicy_noFlag_shortcutRemoved() 784 throws Exception { 785 if (!isBubblesFeatureSupported()) { 786 return; 787 } 788 789 try { 790 allowAllNotificationsToBubble(); 791 createDynamicShortcut(); 792 Notification.Builder nb = getConversationNotification(); 793 nb.setBubbleMetadata(new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID) 794 .build()); 795 796 sendAndVerifyBubble(42, nb, true /* shouldBeBubble */); 797 mListener.resetData(); 798 799 deleteShortcuts(); 800 verifyNotificationBubbleState(42, false /* should be bubble */); 801 } finally { 802 deleteShortcuts(); 803 } 804 } 805 806 @Test testNotificationManagerBubbleNotificationSuppression()807 public void testNotificationManagerBubbleNotificationSuppression() throws Exception { 808 if (!isBubblesFeatureSupported()) { 809 return; 810 } 811 try { 812 allowAllNotificationsToBubble(); 813 814 createDynamicShortcut(); 815 setUpNotifListener(); 816 817 // make ourselves foreground so we can specify suppress notification flag 818 SendBubbleActivity a = startSendBubbleActivity(); 819 820 // send the bubble with notification suppressed 821 a.sendBubble(BUBBLE_NOTIF_ID, false /* autoExpand */, true /* suppressNotif */); 822 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, true /* shouldBeBubble */); 823 824 // check for the notification 825 StatusBarNotification sbnSuppressed = mListener.mPosted.get(0); 826 assertNotNull(sbnSuppressed); 827 // check for suppression state 828 Notification.BubbleMetadata metadata = 829 sbnSuppressed.getNotification().getBubbleMetadata(); 830 assertNotNull(metadata); 831 assertTrue(metadata.isNotificationSuppressed()); 832 833 mListener.resetData(); 834 835 // send the bubble with notification NOT suppressed 836 a.sendBubble(BUBBLE_NOTIF_ID, false /* autoExpand */, false /* suppressNotif */); 837 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, true /* shouldBubble */); 838 839 // check for the notification 840 StatusBarNotification sbnNotSuppressed = mListener.mPosted.get(0); 841 assertNotNull(sbnNotSuppressed); 842 // check for suppression state 843 metadata = sbnNotSuppressed.getNotification().getBubbleMetadata(); 844 assertNotNull(metadata); 845 assertFalse(metadata.isNotificationSuppressed()); 846 } finally { 847 cleanupSendBubbleActivity(); 848 deleteShortcuts(); 849 } 850 } 851 852 @Test testNotificationManagerBubble_checkIsBubbled_pendingIntent()853 public void testNotificationManagerBubble_checkIsBubbled_pendingIntent() 854 throws Exception { 855 if (!isBubblesFeatureSupported()) { 856 return; 857 } 858 try { 859 allowAllNotificationsToBubble(); 860 861 createDynamicShortcut(); 862 setUpNotifListener(); 863 864 SendBubbleActivity a = startSendBubbleActivity(); 865 866 // Prep to find bubbled activity 867 Instrumentation.ActivityMonitor monitor = startBubbledActivityMonitor(); 868 869 a.sendBubble(BUBBLE_NOTIF_ID, true /* autoExpand */, false /* suppressNotif */); 870 871 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, true /* shouldBeBubble */); 872 873 BubbledActivity activity = (BubbledActivity) monitor.waitForActivity(); 874 assertTrue(activity.isLaunchedFromBubble()); 875 } finally { 876 deleteShortcuts(); 877 cleanupSendBubbleActivity(); 878 } 879 } 880 881 @Test testNotificationManagerBubble_checkIsBubbled_shortcut()882 public void testNotificationManagerBubble_checkIsBubbled_shortcut() 883 throws Exception { 884 if (!isBubblesFeatureSupported()) { 885 return; 886 } 887 try { 888 allowAllNotificationsToBubble(); 889 890 createDynamicShortcut(); 891 setUpNotifListener(); 892 893 SendBubbleActivity a = startSendBubbleActivity(); 894 895 // Prep to find bubbled activity 896 Instrumentation.ActivityMonitor monitor = startBubbledActivityMonitor(); 897 898 a.sendBubble(BUBBLE_NOTIF_ID, true /* autoExpand */, 899 false /* suppressNotif */, 900 false /* suppressBubble */, 901 true /* useShortcut */, 902 true /* setLocus */); 903 904 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, true /* shouldBeBubble */); 905 906 BubbledActivity activity = (BubbledActivity) monitor.waitForActivity(); 907 assertTrue(activity.isLaunchedFromBubble()); 908 } finally { 909 deleteShortcuts(); 910 cleanupSendBubbleActivity(); 911 } 912 } 913 914 /** Verifies the bubble is suppressed when it should be. */ 915 @Test testNotificationManagerBubble_setSuppressBubble()916 public void testNotificationManagerBubble_setSuppressBubble() 917 throws Exception { 918 if (!isBubblesFeatureSupported()) { 919 return; 920 } 921 try { 922 allowAllNotificationsToBubble(); 923 924 createDynamicShortcut(); 925 setUpNotifListener(); 926 927 final int notifId = 3; 928 929 // Make a bubble 930 SendBubbleActivity a = startSendBubbleActivity(); 931 a.sendBubble(notifId, 932 false /* autoExpand */, 933 false /* suppressNotif */, 934 true /* suppressBubble */); 935 936 verifyNotificationBubbleState(notifId, true /* shouldBeBubble */); 937 mListener.resetData(); 938 939 // Launch same activity as whats in the bubble 940 BubbledActivity activity = startBubbleActivity(notifId); 941 942 // It should have the locusId 943 assertEquals(new LocusId(String.valueOf(notifId)), 944 activity.getLocusId()); 945 946 // notif gets posted with update, so wait 947 verifyNotificationBubbleState(notifId, true /* shouldBeBubble */); 948 949 // Bubble should have suppressed flag 950 StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, notifId, 951 SEARCH_TYPE.POSTED); 952 assertTrue(sbn.getNotification().getBubbleMetadata().isBubbleSuppressable()); 953 assertTrue(sbn.getNotification().getBubbleMetadata().isBubbleSuppressed()); 954 } finally { 955 deleteShortcuts(); 956 cleanupSendBubbleActivity(); 957 } 958 } 959 960 /** Verifies the bubble is not suppressed if dev didn't specify suppressable */ 961 @Test testNotificationManagerBubble_setSuppressBubble_notSuppressable()962 public void testNotificationManagerBubble_setSuppressBubble_notSuppressable() 963 throws Exception { 964 if (!isBubblesFeatureSupported()) { 965 return; 966 } 967 try { 968 allowAllNotificationsToBubble(); 969 970 createDynamicShortcut(); 971 setUpNotifListener(); 972 973 // Make a bubble 974 SendBubbleActivity a = startSendBubbleActivity(); 975 a.sendBubble(BUBBLE_NOTIF_ID, 976 false /* autoExpand */, 977 false /* suppressNotif */, 978 false /* suppressBubble */); 979 980 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, true /* shouldBeBubble */); 981 mListener.resetData(); 982 983 // Launch same activity as whats in the bubble 984 BubbledActivity activity = startBubbleActivity(BUBBLE_NOTIF_ID); 985 986 // It should have the locusId 987 assertEquals(new LocusId(String.valueOf(BUBBLE_NOTIF_ID)), 988 activity.getLocusId()); 989 990 // Wait a little (if it wrongly updates it'd be a new post so wait for that)) 991 sleep(); 992 assertTrue(mListener.mPosted.isEmpty()); 993 994 // Bubble should not be suppressed 995 StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, 996 BUBBLE_NOTIF_ID, SEARCH_TYPE.LISTENER); 997 assertFalse(sbn.getNotification().getBubbleMetadata().isBubbleSuppressable()); 998 assertFalse(sbn.getNotification().getBubbleMetadata().isBubbleSuppressed()); 999 } finally { 1000 deleteShortcuts(); 1001 cleanupSendBubbleActivity(); 1002 } 1003 } 1004 1005 /** Verifies the bubble is not suppressed if the activity doesn't have a locusId. */ 1006 @Test testNotificationManagerBubble_setSuppressBubble_activityNoLocusId()1007 public void testNotificationManagerBubble_setSuppressBubble_activityNoLocusId() 1008 throws Exception { 1009 if (!isBubblesFeatureSupported()) { 1010 return; 1011 } 1012 try { 1013 allowAllNotificationsToBubble(); 1014 1015 createDynamicShortcut(); 1016 setUpNotifListener(); 1017 1018 // Make a bubble 1019 SendBubbleActivity a = startSendBubbleActivity(); 1020 a.sendBubble(BUBBLE_NOTIF_ID, 1021 false /* autoExpand */, 1022 false /* suppressNotif */, 1023 true /* suppressBubble */); 1024 1025 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, true /* shouldBeBubble */); 1026 mListener.resetData(); 1027 1028 // Launch same activity as whats in the bubble 1029 BubbledActivity activity = startBubbleActivity(BUBBLE_NOTIF_ID, false /* addLocusId */); 1030 1031 // It shouldn't have the locusId 1032 assertNull(activity.getLocusId()); 1033 1034 // Wait a little (if it wrongly updates it'd be a new post so wait for that)) 1035 sleep(); 1036 assertTrue(mListener.mPosted.isEmpty()); 1037 1038 // Bubble should not be suppressed 1039 StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, 1040 BUBBLE_NOTIF_ID, SEARCH_TYPE.LISTENER); 1041 assertTrue(sbn.getNotification().getBubbleMetadata().isBubbleSuppressable()); 1042 assertFalse(sbn.getNotification().getBubbleMetadata().isBubbleSuppressed()); 1043 } finally { 1044 deleteShortcuts(); 1045 cleanupSendBubbleActivity(); 1046 } 1047 } 1048 1049 /** Verifies the bubble is not suppressed if the notification doesn't have a locusId. */ 1050 @Test testNotificationManagerBubble_setSuppressBubble_notificationNoLocusId()1051 public void testNotificationManagerBubble_setSuppressBubble_notificationNoLocusId() 1052 throws Exception { 1053 if (!isBubblesFeatureSupported()) { 1054 return; 1055 } 1056 try { 1057 allowAllNotificationsToBubble(); 1058 1059 createDynamicShortcut(); 1060 setUpNotifListener(); 1061 1062 // Make a bubble 1063 SendBubbleActivity a = startSendBubbleActivity(); 1064 a.sendBubble(BUBBLE_NOTIF_ID, 1065 false /* autoExpand */, 1066 false /* suppressNotif */, 1067 true /* suppressBubble */, 1068 false /* useShortcut */, 1069 false /* setLocusId */); 1070 1071 verifyNotificationBubbleState(BUBBLE_NOTIF_ID, true /* shouldBeBubble */); 1072 mListener.resetData(); 1073 1074 // Launch same activity as whats in the bubble 1075 BubbledActivity activity = startBubbleActivity(BUBBLE_NOTIF_ID, true /* addLocusId */); 1076 1077 // Activity has the locus 1078 assertNotNull(activity.getLocusId()); 1079 1080 // Wait a little (if it wrongly updates it'd be a new post so wait for that)) 1081 sleep(); 1082 assertTrue(mListener.mPosted.isEmpty()); 1083 1084 // Bubble should not be suppressed & not have a locusId 1085 StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, 1086 BUBBLE_NOTIF_ID, SEARCH_TYPE.LISTENER); 1087 assertNull(sbn.getNotification().getLocusId()); 1088 assertTrue(sbn.getNotification().getBubbleMetadata().isBubbleSuppressable()); 1089 assertFalse(sbn.getNotification().getBubbleMetadata().isBubbleSuppressed()); 1090 } finally { 1091 deleteShortcuts(); 1092 cleanupSendBubbleActivity(); 1093 } 1094 } 1095 1096 /** Verifies the bubble is unsuppressed when the locus activity is hidden. */ 1097 @Test testNotificationManagerBubble_setSuppressBubble_dismissLocusActivity()1098 public void testNotificationManagerBubble_setSuppressBubble_dismissLocusActivity() 1099 throws Exception { 1100 if (!isBubblesFeatureSupported()) { 1101 return; 1102 } 1103 try { 1104 allowAllNotificationsToBubble(); 1105 1106 createDynamicShortcut(); 1107 setUpNotifListener(); 1108 1109 final int notifId = 2; 1110 1111 // Make a bubble 1112 SendBubbleActivity a = startSendBubbleActivity(); 1113 a.sendBubble(notifId, 1114 false /* autoExpand */, 1115 false /* suppressNotif */, 1116 true /* suppressBubble */); 1117 1118 verifyNotificationBubbleState(notifId, true); 1119 StatusBarNotification sbn = mNotificationHelper.findPostedNotification(null, notifId, 1120 SEARCH_TYPE.POSTED); 1121 assertTrue(sbn.getNotification().getBubbleMetadata().isBubbleSuppressable()); 1122 assertFalse(sbn.getNotification().getBubbleMetadata().isBubbleSuppressed()); 1123 mListener.resetData(); 1124 1125 // Launch same activity as whats in the bubble 1126 BubbledActivity activity = startBubbleActivity(notifId); 1127 1128 // It should have the locusId 1129 assertEquals(new LocusId(String.valueOf(notifId)), activity.getLocusId()); 1130 1131 // notif gets posted with update, so wait 1132 verifyNotificationBubbleState(notifId, true /* shouldBeBubble */); 1133 // Bubble should have suppressed flag 1134 sbn = mNotificationHelper.findPostedNotification(null, notifId, SEARCH_TYPE.LISTENER); 1135 assertTrue(sbn.getNotification().getBubbleMetadata().isBubbleSuppressable()); 1136 assertTrue(sbn.getNotification().getBubbleMetadata().isBubbleSuppressed()); 1137 mListener.resetData(); 1138 1139 activity.finish(); 1140 1141 // notif gets posted with update, so wait 1142 verifyNotificationBubbleState(notifId, true /* shouldBeBubble */); 1143 sbn = mNotificationHelper.findPostedNotification(null, notifId, SEARCH_TYPE.LISTENER); 1144 assertTrue(sbn.getNotification().getBubbleMetadata().isBubbleSuppressable()); 1145 assertFalse(sbn.getNotification().getBubbleMetadata().isBubbleSuppressed()); 1146 } finally { 1147 deleteShortcuts(); 1148 cleanupSendBubbleActivity(); 1149 } 1150 } 1151 1152 /** Verifies that a regular activity can't specify a bubble in ActivityOptions */ 1153 @Test 1154 @SuppressWarnings("AssertThrowsMultipleStatements") testNotificationManagerBubble_launchBubble_activityOptions_fails()1155 public void testNotificationManagerBubble_launchBubble_activityOptions_fails() 1156 throws Exception { 1157 if (!isBubblesFeatureSupported()) { 1158 return; 1159 } 1160 try { 1161 // Start test activity 1162 SendBubbleActivity activity = startSendBubbleActivity(); 1163 assertFalse(activity.isLaunchedFromBubble()); 1164 1165 // Should have exception 1166 assertThrows(SecurityException.class, () -> { 1167 Intent i = new Intent(mContext, BubbledActivity.class); 1168 ActivityOptions options = ActivityOptions.makeBasic(); 1169 Bundle b = options.toBundle(); 1170 b.putBoolean("android.activity.launchTypeBubble", true); 1171 activity.startActivity(i, b); 1172 }); 1173 } finally { 1174 cleanupSendBubbleActivity(); 1175 } 1176 } 1177 } 1178