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.jobscheduler.cts; 18 19 import static android.app.job.JobInfo.NETWORK_TYPE_ANY; 20 import static android.jobscheduler.cts.TestAppInterface.TEST_APP_PACKAGE; 21 22 import static com.android.compatibility.common.util.TestUtils.waitUntil; 23 24 import static org.junit.Assert.assertTrue; 25 26 import android.app.Notification; 27 import android.app.NotificationChannel; 28 import android.app.NotificationManager; 29 import android.app.job.JobInfo; 30 import android.app.job.JobParameters; 31 import android.app.job.JobService; 32 import android.content.pm.ApplicationInfo; 33 import android.jobscheduler.cts.UserInitiatedJobTest.WatchUidRunner; 34 import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver; 35 import android.provider.Settings; 36 import android.service.notification.StatusBarNotification; 37 38 import androidx.test.InstrumentationRegistry; 39 import androidx.test.filters.LargeTest; 40 41 import com.android.compatibility.common.util.AnrMonitor; 42 import com.android.compatibility.common.util.SystemUtil; 43 44 import java.util.Collections; 45 import java.util.Map; 46 47 /** 48 * Tests related to attaching notifications to jobs via 49 * {@link JobService#setNotification(JobParameters, int, Notification, int)} 50 */ 51 public class NotificationTest extends BaseJobSchedulerTest { 52 private static final int JOB_ID = NotificationTest.class.hashCode(); 53 private static final long DEFAULT_WAIT_TIMEOUT_MS = 2_000; 54 private static final String NOTIFICATION_CHANNEL_ID = 55 NotificationTest.class.getSimpleName() + "_channel"; 56 57 private NotificationManager mNotificationManager; 58 private NetworkingHelper mNetworkingHelper; 59 60 @Override setUp()61 public void setUp() throws Exception { 62 super.setUp(); 63 mNotificationManager = getContext().getSystemService(NotificationManager.class); 64 NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, 65 NotificationTest.class.getSimpleName(), NotificationManager.IMPORTANCE_DEFAULT); 66 mNotificationManager.createNotificationChannel(channel); 67 mNetworkingHelper = 68 new NetworkingHelper(InstrumentationRegistry.getInstrumentation(), mContext); 69 } 70 71 @Override tearDown()72 public void tearDown() throws Exception { 73 mJobScheduler.cancel(JOB_ID); 74 mNotificationManager.cancelAll(); 75 mNetworkingHelper.tearDown(); 76 77 // The super method should be called at the end. 78 super.tearDown(); 79 } 80 testNotificationJobEndDetach()81 public void testNotificationJobEndDetach() throws Exception { 82 mNotificationManager.cancelAll(); 83 final int notificationId = 123; 84 JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent).build(); 85 86 Notification notification = new Notification.Builder(getContext(), NOTIFICATION_CHANNEL_ID) 87 .setContentTitle("test title") 88 .setSmallIcon(android.R.mipmap.sym_def_app_icon) 89 .setContentText("test content") 90 .build(); 91 92 kTestEnvironment.setExpectedExecutions(1); 93 kTestEnvironment.setContinueAfterStart(); 94 kTestEnvironment.setNotificationAtStart(notificationId, notification, 95 JobService.JOB_END_NOTIFICATION_POLICY_DETACH); 96 mJobScheduler.schedule(jobInfo); 97 assertTrue("Job didn't start", kTestEnvironment.awaitExecution()); 98 99 waitUntil("Notification wasn't posted", 15 /* seconds */, 100 () -> { 101 StatusBarNotification[] activeNotifications = 102 mNotificationManager.getActiveNotifications(); 103 return activeNotifications.length == 1 104 && activeNotifications[0].getId() == notificationId; 105 }); 106 107 kTestEnvironment.setExpectedStopped(); 108 mJobScheduler.cancel(JOB_ID); 109 assertTrue(kTestEnvironment.awaitStopped()); 110 111 Thread.sleep(1000); // Wait a bit for NotificationManager to catch up 112 // Notification should remain 113 StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications(); 114 assertEquals(1, activeNotifications.length); 115 assertEquals(notificationId, activeNotifications[0].getId()); 116 } 117 testNotificationJobEndRemove()118 public void testNotificationJobEndRemove() throws Exception { 119 mNotificationManager.cancelAll(); 120 final int notificationId = 123; 121 JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent).build(); 122 123 Notification notification = new Notification.Builder(getContext(), NOTIFICATION_CHANNEL_ID) 124 .setContentTitle("test title") 125 .setSmallIcon(android.R.mipmap.sym_def_app_icon) 126 .setContentText("test content") 127 .build(); 128 129 kTestEnvironment.setExpectedExecutions(1); 130 kTestEnvironment.setContinueAfterStart(); 131 kTestEnvironment.setNotificationAtStart(notificationId, notification, 132 JobService.JOB_END_NOTIFICATION_POLICY_REMOVE); 133 mJobScheduler.schedule(jobInfo); 134 assertTrue("Job didn't start", kTestEnvironment.awaitExecution()); 135 136 waitUntil("Notification wasn't posted", 15 /* seconds */, 137 () -> { 138 StatusBarNotification[] activeNotifications = 139 mNotificationManager.getActiveNotifications(); 140 return activeNotifications.length == 1 141 && activeNotifications[0].getId() == notificationId; 142 }); 143 144 kTestEnvironment.setExpectedStopped(); 145 mJobScheduler.cancel(JOB_ID); 146 assertTrue(kTestEnvironment.awaitStopped()); 147 148 waitUntil("Notification wasn't removed", 15 /* seconds */, 149 () -> { 150 // Notification should be gone 151 return mNotificationManager.getActiveNotifications().length == 0; 152 }); 153 } 154 testNotificationRemovedOnForceStop()155 public void testNotificationRemovedOnForceStop() throws Exception { 156 mNetworkingHelper.setAllNetworksEnabled(true); 157 try (TestAppInterface mTestAppInterface = new TestAppInterface(mContext, JOB_ID); 158 TestNotificationListener.NotificationHelper notificationHelper = 159 new TestNotificationListener.NotificationHelper( 160 mContext, TestAppInterface.TEST_APP_PACKAGE)) { 161 mTestAppInterface.startAndKeepTestActivity(true); 162 mTestAppInterface.scheduleJob( 163 Map.of( 164 TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, true, 165 TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION, true 166 ), 167 Map.of( 168 TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION_JOB_END_POLICY, 169 JobService.JOB_END_NOTIFICATION_POLICY_DETACH, 170 TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, NETWORK_TYPE_ANY 171 )); 172 173 assertTrue("Job did not start after scheduling", 174 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT_MS)); 175 176 StatusBarNotification jobNotification = notificationHelper.getNotification(); 177 assertNotNull(jobNotification); 178 179 mTestAppInterface.forceStopApp(); 180 181 notificationHelper.assertNotificationsRemoved(); 182 } 183 } 184 testNotificationRemovedOnPackageRestriction()185 public void testNotificationRemovedOnPackageRestriction() throws Exception { 186 String initialActivityManagerConstants = null; 187 try (TestAppInterface testAppInterface = new TestAppInterface(mContext, JOB_ID); 188 TestNotificationListener.NotificationHelper notificationHelper = 189 new TestNotificationListener.NotificationHelper( 190 mContext, TestAppInterface.TEST_APP_PACKAGE)) { 191 initialActivityManagerConstants = 192 Settings.Global.getString(getContext().getContentResolver(), 193 Settings.Global.ACTIVITY_MANAGER_CONSTANTS); 194 SystemUtil.runShellCommand("am set-deterministic-uid-idle true"); 195 // Set background_settle_time to 0 so that the transition from UID active to UID idle 196 // happens quickly. 197 Settings.Global.putString(getContext().getContentResolver(), 198 Settings.Global.ACTIVITY_MANAGER_CONSTANTS, "background_settle_time=0"); 199 200 testAppInterface.setTestPackageRestricted(true); 201 testAppInterface.startAndKeepTestActivity(true); 202 testAppInterface.scheduleJob( 203 Map.of(TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION, true), 204 Map.of( 205 TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION_JOB_END_POLICY, 206 JobService.JOB_END_NOTIFICATION_POLICY_DETACH 207 )); 208 209 assertTrue("Job did not start after scheduling", 210 testAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT_MS)); 211 212 StatusBarNotification jobNotification = notificationHelper.getNotification(); 213 assertNotNull(jobNotification); 214 215 final ApplicationInfo testAppInfo = 216 mContext.getPackageManager().getApplicationInfo(TEST_APP_PACKAGE, 0); 217 try (WatchUidRunner uidWatcher = new WatchUidRunner( 218 InstrumentationRegistry.getInstrumentation(), testAppInfo.uid)) { 219 // Close the activity so the app isn't considered TOP. 220 testAppInterface.closeActivity(true); 221 uidWatcher.waitFor(UserInitiatedJobTest.WatchUidRunner.CMD_IDLE); 222 Thread.sleep(1000); // Wait a bit for JS to process. 223 } 224 225 assertTrue(testAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT_MS)); 226 notificationHelper.assertNotificationsRemoved(); 227 } finally { 228 Settings.Global.putString(getContext().getContentResolver(), 229 Settings.Global.ACTIVITY_MANAGER_CONSTANTS, initialActivityManagerConstants); 230 SystemUtil.runShellCommand("am set-deterministic-uid-idle false"); 231 } 232 } 233 testNotificationRemovedOnTaskManagerStop()234 public void testNotificationRemovedOnTaskManagerStop() throws Exception { 235 mNetworkingHelper.setAllNetworksEnabled(true); 236 try (TestAppInterface mTestAppInterface = new TestAppInterface(mContext, JOB_ID); 237 TestNotificationListener.NotificationHelper notificationHelper = 238 new TestNotificationListener.NotificationHelper( 239 mContext, TestAppInterface.TEST_APP_PACKAGE)) { 240 mTestAppInterface.startAndKeepTestActivity(true); 241 mTestAppInterface.scheduleJob( 242 Map.of( 243 TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, true, 244 TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION, true 245 ), 246 Map.of( 247 TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION_JOB_END_POLICY, 248 JobService.JOB_END_NOTIFICATION_POLICY_DETACH, 249 TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, NETWORK_TYPE_ANY 250 )); 251 252 assertTrue("Job did not start after scheduling", 253 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT_MS)); 254 255 StatusBarNotification jobNotification = notificationHelper.getNotification(); 256 assertNotNull(jobNotification); 257 258 // Use the same stop reasons as a Task Manager stop. 259 mTestAppInterface.stopJob(JobParameters.STOP_REASON_USER, 260 JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP); 261 262 notificationHelper.assertNotificationsRemoved(); 263 } 264 } 265 266 /** 267 * Test that an ANR happens if the app is required to show a notification 268 * but doesn't provide one. 269 */ testNotification_userInitiated_anrWhenNotProvided()270 public void testNotification_userInitiated_anrWhenNotProvided() throws Exception { 271 mNetworkingHelper.setAllNetworksEnabled(true); 272 try (TestAppInterface testAppInterface = new TestAppInterface(mContext, JOB_ID); 273 AnrMonitor monitor = AnrMonitor.start(InstrumentationRegistry.getInstrumentation(), 274 TEST_APP_PACKAGE); 275 TestNotificationListener.NotificationHelper notificationHelper = 276 new TestNotificationListener.NotificationHelper(mContext, TEST_APP_PACKAGE)) { 277 278 testAppInterface.postUiInitiatingNotification( 279 Map.of( 280 TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, true, 281 TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION, false 282 ), 283 Map.of(TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, NETWORK_TYPE_ANY)); 284 285 // Clicking on the notification should put the app into a BAL approved state. 286 notificationHelper.clickNotification(); 287 288 assertTrue("Job did not start after scheduling", 289 testAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT_MS)); 290 291 // Confirm ANR 292 monitor.waitForAnrAndReturnUptime(30_000); 293 } 294 } 295 296 /** 297 * Test that no ANR happens if the app is required to show a notification and it provides one. 298 */ 299 @LargeTest testNotification_userInitiated_noAnrWhenProvided()300 public void testNotification_userInitiated_noAnrWhenProvided() throws Exception { 301 mNetworkingHelper.setAllNetworksEnabled(true); 302 try (TestAppInterface testAppInterface = new TestAppInterface(mContext, JOB_ID); 303 AnrMonitor monitor = AnrMonitor.start(InstrumentationRegistry.getInstrumentation(), 304 TEST_APP_PACKAGE); 305 TestNotificationListener.NotificationHelper notificationHelper = 306 new TestNotificationListener.NotificationHelper(mContext, TEST_APP_PACKAGE)) { 307 308 testAppInterface.postUiInitiatingNotification( 309 Map.of( 310 TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, true, 311 TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION, true 312 ), 313 Map.of(TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, NETWORK_TYPE_ANY)); 314 315 // Clicking on the notification should put the app into a BAL approved state. 316 notificationHelper.clickNotification(); 317 318 assertTrue("Job did not start after scheduling", 319 testAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT_MS)); 320 321 // Confirm no ANR 322 monitor.assertNoAnr(25_000); 323 } 324 } 325 326 /** 327 * Test that no ANR happens if the app is not required to show a notification 328 * and it doesn't provide one. 329 */ 330 @LargeTest testNotification_regular_noAnrWhenNotProvided()331 public void testNotification_regular_noAnrWhenNotProvided() throws Exception { 332 try (TestAppInterface testAppInterface = new TestAppInterface(mContext, JOB_ID); 333 AnrMonitor monitor = AnrMonitor.start(InstrumentationRegistry.getInstrumentation(), 334 TEST_APP_PACKAGE); 335 TestNotificationListener.NotificationHelper notificationHelper = 336 new TestNotificationListener.NotificationHelper(mContext, TEST_APP_PACKAGE)) { 337 338 testAppInterface.postUiInitiatingNotification( 339 Map.of(TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION, false), 340 Collections.emptyMap()); 341 342 notificationHelper.clickNotification(); 343 344 assertTrue("Job did not start after scheduling", 345 testAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT_MS)); 346 347 // Confirm no ANR 348 monitor.assertNoAnr(25_000); 349 } 350 } 351 testUserInitiatedJob_hasUijNotificationFlag()352 public void testUserInitiatedJob_hasUijNotificationFlag() throws Exception { 353 mNetworkingHelper.setAllNetworksEnabled(true); 354 try (TestAppInterface mTestAppInterface = new TestAppInterface(mContext, JOB_ID); 355 TestNotificationListener.NotificationHelper notificationHelper = 356 new TestNotificationListener.NotificationHelper( 357 mContext, TestAppInterface.TEST_APP_PACKAGE)) { 358 mTestAppInterface.startAndKeepTestActivity(true); 359 mTestAppInterface.scheduleJob( 360 Map.of( 361 TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, true, 362 TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION, true 363 ), 364 Map.of(TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, NETWORK_TYPE_ANY)); 365 366 assertTrue("Job did not start after scheduling", 367 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT_MS)); 368 369 StatusBarNotification jobNotification = notificationHelper.getNotification(); 370 assertNotNull(jobNotification); 371 assertTrue("A user-initiated job notification should have the UIJ flag", 372 jobNotification.getNotification().isUserInitiatedJob()); 373 } 374 } 375 testNonUserInitiatedJob_doesNotHaveUijNotificationFlag()376 public void testNonUserInitiatedJob_doesNotHaveUijNotificationFlag() throws Exception { 377 try (TestAppInterface mTestAppInterface = new TestAppInterface(mContext, JOB_ID); 378 TestNotificationListener.NotificationHelper notificationHelper = 379 new TestNotificationListener.NotificationHelper( 380 mContext, TestAppInterface.TEST_APP_PACKAGE)) { 381 mTestAppInterface.startAndKeepTestActivity(true); 382 mTestAppInterface.scheduleJob( 383 Map.of(TestJobSchedulerReceiver.EXTRA_SET_NOTIFICATION, true), 384 Collections.emptyMap()); 385 386 assertTrue("Job did not start after scheduling", 387 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT_MS)); 388 389 StatusBarNotification jobNotification = notificationHelper.getNotification(); 390 assertNotNull(jobNotification); 391 assertFalse("A non user-initiated job notification should not have the UIJ flag", 392 jobNotification.getNotification().isUserInitiatedJob()); 393 } 394 } 395 396 /** 397 * Test that a notification associated with a user-initiated job cannot be cancelled and that 398 * its notification channel cannot be deleted. 399 */ testUserInitiatedJobNotificationBehavior()400 public void testUserInitiatedJobNotificationBehavior() throws Exception { 401 mNotificationManager.cancelAll(); 402 mNetworkingHelper.setAllNetworksEnabled(true); 403 startAndKeepTestActivity(); 404 final int notificationId = 123; 405 JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent) 406 .setUserInitiated(true) 407 .setRequiredNetworkType(NETWORK_TYPE_ANY) 408 .build(); 409 410 Notification notification = new Notification.Builder(getContext(), NOTIFICATION_CHANNEL_ID) 411 .setContentTitle("test title") 412 .setSmallIcon(android.R.mipmap.sym_def_app_icon) 413 .setContentText("test content") 414 .build(); 415 416 kTestEnvironment.setExpectedExecutions(1); 417 kTestEnvironment.setContinueAfterStart(); 418 kTestEnvironment.setNotificationAtStart(notificationId, notification, 419 JobService.JOB_END_NOTIFICATION_POLICY_REMOVE); 420 mJobScheduler.schedule(jobInfo); 421 runSatisfiedJob(JOB_ID); 422 assertTrue("Job didn't start", kTestEnvironment.awaitExecution()); 423 424 waitUntil("Notification wasn't posted", 15 /* seconds */, 425 () -> { 426 StatusBarNotification[] activeNotifications = 427 mNotificationManager.getActiveNotifications(); 428 return activeNotifications.length == 1 429 && activeNotifications[0].getId() == notificationId; 430 }); 431 432 mNotificationManager.cancel(notificationId); 433 waitUntil("A user-initiated job notification should not be cancellable by apps.", 434 5 /* seconds */, 435 () -> { 436 StatusBarNotification[] activeNotifications = 437 mNotificationManager.getActiveNotifications(); 438 return activeNotifications.length == 1 439 && activeNotifications[0].getId() == notificationId; 440 }); 441 442 try { 443 mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID); 444 fail("A notification channel associated with a user-initiated job " 445 + "should not be cancellable by apps."); 446 } catch (SecurityException expected) { 447 assertNotNull(mNotificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)); 448 } 449 } 450 451 /** 452 * Test that a notification associated with a non user-initiated job can be cancelled and that 453 * its notification channel can be deleted. 454 */ testNonUserInitiatedJobNotificationBehavior()455 public void testNonUserInitiatedJobNotificationBehavior() throws Exception { 456 mNotificationManager.cancelAll(); 457 mNetworkingHelper.setAllNetworksEnabled(true); 458 startAndKeepTestActivity(); 459 final int notificationId = 123; 460 JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent).build(); 461 462 Notification notification = new Notification.Builder(getContext(), NOTIFICATION_CHANNEL_ID) 463 .setContentTitle("test title") 464 .setSmallIcon(android.R.mipmap.sym_def_app_icon) 465 .setContentText("test content") 466 .build(); 467 468 kTestEnvironment.setExpectedExecutions(1); 469 kTestEnvironment.setContinueAfterStart(); 470 kTestEnvironment.setNotificationAtStart(notificationId, notification, 471 JobService.JOB_END_NOTIFICATION_POLICY_REMOVE); 472 mJobScheduler.schedule(jobInfo); 473 runSatisfiedJob(JOB_ID); 474 assertTrue("Job didn't start", kTestEnvironment.awaitExecution()); 475 476 waitUntil("Notification wasn't posted", 15 /* seconds */, 477 () -> { 478 StatusBarNotification[] activeNotifications = 479 mNotificationManager.getActiveNotifications(); 480 return activeNotifications.length == 1 481 && activeNotifications[0].getId() == notificationId; 482 }); 483 484 mNotificationManager.cancel(notificationId); 485 waitUntil("A non user-initiated job notification should be cancellable by apps.", 486 15 /* seconds */, 487 () -> { 488 // Notification should be gone 489 return mNotificationManager.getActiveNotifications().length == 0; 490 }); 491 492 try { 493 mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID); 494 assertNull(mNotificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)); 495 } catch (SecurityException e) { 496 fail("A notification channel associated with a non user-initiated job " 497 + "should be cancellable by apps."); 498 } 499 } 500 } 501