1 // Copyright 2017 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.download; 6 7 import static android.app.Service.STOP_FOREGROUND_DETACH; 8 import static android.app.Service.STOP_FOREGROUND_REMOVE; 9 10 import static junit.framework.Assert.assertEquals; 11 12 import static org.chromium.chrome.browser.download.DownloadSnackbarController.INVALID_NOTIFICATION_ID; 13 14 import android.app.Notification; 15 16 import androidx.annotation.IntDef; 17 import androidx.core.app.ServiceCompat; 18 import androidx.test.filters.SmallTest; 19 20 import org.junit.Before; 21 import org.junit.Test; 22 import org.junit.runner.RunWith; 23 24 import org.chromium.base.test.util.Feature; 25 import org.chromium.chrome.browser.notifications.NotificationWrapperBuilderFactory; 26 import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions; 27 import org.chromium.chrome.test.ChromeJUnit4ClassRunner; 28 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.List; 34 35 /** 36 * Test for DownloadForegroundService. 37 */ 38 @RunWith(ChromeJUnit4ClassRunner.class) 39 public class DownloadForegroundServiceTest { 40 private static final int FAKE_DOWNLOAD_ID1 = 1; 41 private static final int FAKE_DOWNLOAD_ID2 = 2; 42 43 private Notification mNotification; 44 private MockDownloadForegroundService mForegroundService; 45 46 /** 47 * Implementation of DownloadForegroundService for testing. 48 * Mimics behavior of DownloadForegroundService except for calls to the actual service. 49 */ 50 public static class MockDownloadForegroundService extends DownloadForegroundServiceImpl { 51 @IntDef({MethodID.START_FOREGROUND, MethodID.STOP_FOREGROUND_FLAGS, 52 MethodID.RELAUNCH_NOTIFICATION}) 53 @Retention(RetentionPolicy.SOURCE) 54 public @interface MethodID { 55 int START_FOREGROUND = 0; 56 int STOP_FOREGROUND_FLAGS = 1; 57 int RELAUNCH_NOTIFICATION = 2; 58 } 59 60 int mTargetSdk = 20; 61 int mStopForegroundFlags = -1; 62 int mRelaunchedNotificationId = INVALID_NOTIFICATION_ID; 63 int mNextNotificationId = INVALID_NOTIFICATION_ID; 64 65 // Used for saving MethodID values. 66 List<Integer> mMethodCalls = new ArrayList<>(); 67 MockDownloadForegroundService()68 public MockDownloadForegroundService() { 69 setService(new DownloadForegroundService()); 70 } 71 72 // Clears stored flags/boolean/id/method calls. Call between tests runs. clearStoredState()73 void clearStoredState() { 74 mStopForegroundFlags = -1; 75 mRelaunchedNotificationId = INVALID_NOTIFICATION_ID; 76 mMethodCalls.clear(); 77 mNextNotificationId = INVALID_NOTIFICATION_ID; 78 } 79 80 @Override startForegroundInternal(int notificationId, Notification notification)81 void startForegroundInternal(int notificationId, Notification notification) { 82 mMethodCalls.add(MethodID.START_FOREGROUND); 83 } 84 85 @Override stopForegroundInternal(int flags)86 void stopForegroundInternal(int flags) { 87 mMethodCalls.add(MethodID.STOP_FOREGROUND_FLAGS); 88 mStopForegroundFlags = flags; 89 } 90 91 @Override relaunchOldNotification(int notificationId, Notification notification)92 void relaunchOldNotification(int notificationId, Notification notification) { 93 mMethodCalls.add(MethodID.RELAUNCH_NOTIFICATION); 94 mRelaunchedNotificationId = notificationId; 95 } 96 97 @Override getCurrentSdk()98 int getCurrentSdk() { 99 return mTargetSdk; 100 } 101 102 @Override getNewNotificationIdFor(int oldNotificationId)103 int getNewNotificationIdFor(int oldNotificationId) { 104 return mNextNotificationId; 105 } 106 } 107 108 @Before setUp()109 public void setUp() { 110 mForegroundService = new MockDownloadForegroundService(); 111 mNotification = 112 NotificationWrapperBuilderFactory 113 .createNotificationWrapperBuilder(true /* preferCompat */, 114 ChromeChannelDefinitions.ChannelId.DOWNLOADS) 115 .setSmallIcon(org.chromium.chrome.R.drawable.ic_file_download_white_24dp) 116 .setContentTitle("fakeContentTitle") 117 .setContentText("fakeContentText") 118 .build(); 119 } 120 121 /** 122 * The expected behavior for start foreground when the API >= 24 is that the old notification is 123 * able to be detached and the new notification pinned without any need for relaunching or 124 * correcting the notification. 125 */ 126 @Test 127 @SmallTest 128 @Feature({"Download"}) testStartForeground_sdkAtLeast24()129 public void testStartForeground_sdkAtLeast24() { 130 mForegroundService.mTargetSdk = 24; 131 List<Integer> expectedMethodCalls = 132 Arrays.asList(MockDownloadForegroundService.MethodID.START_FOREGROUND); 133 134 // Test the case where there is no other pinned notification and the service starts. 135 mForegroundService.startOrUpdateForegroundService( 136 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 137 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 138 assertEquals(INVALID_NOTIFICATION_ID, mForegroundService.mRelaunchedNotificationId); 139 140 mForegroundService.clearStoredState(); 141 142 // Test the case where there is another pinned notification and the service needs to start. 143 mForegroundService.startOrUpdateForegroundService( 144 FAKE_DOWNLOAD_ID2, mNotification, FAKE_DOWNLOAD_ID1, mNotification, false); 145 expectedMethodCalls = 146 Arrays.asList(MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS, 147 MockDownloadForegroundService.MethodID.START_FOREGROUND); 148 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 149 assertEquals(STOP_FOREGROUND_DETACH, mForegroundService.mStopForegroundFlags); 150 assertEquals(INVALID_NOTIFICATION_ID, mForegroundService.mRelaunchedNotificationId); 151 152 mForegroundService.clearStoredState(); 153 154 // Test the case where there is another pinned notification but we are killing it. 155 mForegroundService.startOrUpdateForegroundService( 156 FAKE_DOWNLOAD_ID2, mNotification, FAKE_DOWNLOAD_ID1, mNotification, true); 157 expectedMethodCalls = 158 Arrays.asList(MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS, 159 MockDownloadForegroundService.MethodID.START_FOREGROUND); 160 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 161 assertEquals(STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 162 assertEquals(INVALID_NOTIFICATION_ID, mForegroundService.mRelaunchedNotificationId); 163 } 164 165 /** 166 * The expected behavior for start foreground when API < 24 is that the foreground is stopped 167 * and, in cases there is a previously pinned notification, it is relaunched. 168 */ 169 @Test 170 @SmallTest 171 @Feature({"Download"}) testStartForeground_sdkLessThan24()172 public void testStartForeground_sdkLessThan24() { 173 List<Integer> expectedMethodCalls = 174 Arrays.asList(MockDownloadForegroundService.MethodID.START_FOREGROUND); 175 176 // Test the case where there is no other pinned notification and the service starts. 177 mForegroundService.startOrUpdateForegroundService( 178 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 179 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 180 assertEquals(INVALID_NOTIFICATION_ID, mForegroundService.mRelaunchedNotificationId); 181 182 mForegroundService.clearStoredState(); 183 184 // Test the case where there is another pinned notification and the service needs to start. 185 mForegroundService.startOrUpdateForegroundService( 186 FAKE_DOWNLOAD_ID2, mNotification, FAKE_DOWNLOAD_ID1, mNotification, false); 187 expectedMethodCalls = Arrays.asList(MockDownloadForegroundService.MethodID.START_FOREGROUND, 188 MockDownloadForegroundService.MethodID.RELAUNCH_NOTIFICATION); 189 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 190 assertEquals(FAKE_DOWNLOAD_ID1, mForegroundService.mRelaunchedNotificationId); 191 192 mForegroundService.clearStoredState(); 193 194 /// Test the case where there is another pinned notification but we are killing it. 195 mForegroundService.startOrUpdateForegroundService( 196 FAKE_DOWNLOAD_ID2, mNotification, FAKE_DOWNLOAD_ID1, mNotification, true); 197 expectedMethodCalls = 198 Arrays.asList(MockDownloadForegroundService.MethodID.START_FOREGROUND); 199 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 200 assertEquals(INVALID_NOTIFICATION_ID, mForegroundService.mRelaunchedNotificationId); 201 } 202 203 /** 204 * The expected behavior for stop foreground when API >= 24 is that only one call is needed, 205 * stop foreground with the correct flag and no notification adjustment is required. 206 */ 207 @Test 208 @SmallTest 209 @Feature({"Download"}) testStopForeground_sdkAtLeast24()210 public void testStopForeground_sdkAtLeast24() { 211 mForegroundService.mTargetSdk = 24; 212 List<Integer> expectedMethodCalls = 213 Arrays.asList(MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS); 214 215 // When the service gets stopped with request to detach but not kill notification (pause). 216 mForegroundService.startOrUpdateForegroundService( 217 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 218 mForegroundService.clearStoredState(); 219 220 mForegroundService.stopDownloadForegroundService( 221 DownloadForegroundServiceImpl.StopForegroundNotification.DETACH, 222 INVALID_NOTIFICATION_ID, null); 223 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 224 assertEquals(STOP_FOREGROUND_DETACH, mForegroundService.mStopForegroundFlags); 225 226 // When the service gets stopped with request to detach and kill (complete/failed). 227 mForegroundService.startOrUpdateForegroundService( 228 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 229 mForegroundService.clearStoredState(); 230 231 mForegroundService.stopDownloadForegroundService( 232 DownloadForegroundServiceImpl.StopForegroundNotification.DETACH, 233 INVALID_NOTIFICATION_ID, null); 234 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 235 assertEquals(STOP_FOREGROUND_DETACH, mForegroundService.mStopForegroundFlags); 236 237 // When the service gets stopped with request to not detach but to kill (cancel). 238 mForegroundService.startOrUpdateForegroundService( 239 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 240 mForegroundService.clearStoredState(); 241 242 mForegroundService.stopDownloadForegroundService( 243 DownloadForegroundServiceImpl.StopForegroundNotification.KILL, 244 INVALID_NOTIFICATION_ID, null); 245 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 246 assertEquals(STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 247 } 248 249 /** 250 * The expected behavior for stop foreground when 24 > API >= 23 is: 251 * - paused: the notification does not get killed and is not handled properly so is persisted. 252 * - complete/failed: the notification gets killed but relaunched. 253 * - cancel: the notification gets killed and not relaunched. 254 */ 255 @Test 256 @SmallTest 257 @Feature({"Download"}) testStopForeground_sdkAtLeast23()258 public void testStopForeground_sdkAtLeast23() { 259 mForegroundService.mTargetSdk = 23; 260 261 // When the service gets stopped with request to detach but not kill notification (pause). 262 mForegroundService.startOrUpdateForegroundService( 263 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 264 mForegroundService.clearStoredState(); 265 266 mForegroundService.stopDownloadForegroundService( 267 DownloadForegroundServiceImpl.StopForegroundNotification.DETACH, FAKE_DOWNLOAD_ID1, 268 mNotification); 269 List<Integer> expectedMethodCalls = 270 Arrays.asList(MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS, 271 MockDownloadForegroundService.MethodID.RELAUNCH_NOTIFICATION); 272 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 273 assertEquals(ServiceCompat.STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 274 275 mForegroundService.clearStoredState(); 276 277 // When the service gets stopped with request to detach and kill (complete/failed). 278 mForegroundService.startOrUpdateForegroundService( 279 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 280 mForegroundService.clearStoredState(); 281 282 mForegroundService.stopDownloadForegroundService( 283 DownloadForegroundServiceImpl.StopForegroundNotification.DETACH, FAKE_DOWNLOAD_ID1, 284 mNotification); 285 expectedMethodCalls = 286 Arrays.asList(MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS, 287 MockDownloadForegroundService.MethodID.RELAUNCH_NOTIFICATION); 288 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 289 assertEquals(ServiceCompat.STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 290 assertEquals(FAKE_DOWNLOAD_ID1, mForegroundService.mRelaunchedNotificationId); 291 292 // When the service gets stopped with request to not detach but to kill (cancel). 293 mForegroundService.startOrUpdateForegroundService( 294 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 295 mForegroundService.clearStoredState(); 296 297 mForegroundService.stopDownloadForegroundService( 298 DownloadForegroundServiceImpl.StopForegroundNotification.KILL, FAKE_DOWNLOAD_ID1, 299 mNotification); 300 expectedMethodCalls = 301 Arrays.asList(MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS); 302 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 303 assertEquals(ServiceCompat.STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 304 } 305 306 /** 307 * The expected behavior for stop foreground when 23 > API >= 21 is similar to the previous case 308 * except that in the case where a relaunch is needed (complete/failed), the relaunch needs to 309 * happen before the service is stopped and requires a "new" notification id. 310 */ 311 @Test 312 @SmallTest 313 @Feature({"Download"}) testStopForeground_sdkAtLeast21()314 public void testStopForeground_sdkAtLeast21() { 315 mForegroundService.mTargetSdk = 21; 316 317 // When the service gets stopped with request to detach but not kill notification (pause). 318 mForegroundService.startOrUpdateForegroundService( 319 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 320 mForegroundService.clearStoredState(); 321 322 mForegroundService.stopDownloadForegroundService( 323 DownloadForegroundServiceImpl.StopForegroundNotification.DETACH, FAKE_DOWNLOAD_ID1, 324 mNotification); 325 List<Integer> expectedMethodCalls = 326 Arrays.asList(MockDownloadForegroundService.MethodID.RELAUNCH_NOTIFICATION, 327 MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS); 328 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 329 assertEquals(ServiceCompat.STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 330 331 // When the service gets stopped with request to detach and kill (complete/failed). 332 mForegroundService.startOrUpdateForegroundService( 333 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 334 mForegroundService.clearStoredState(); 335 336 mForegroundService.mNextNotificationId = FAKE_DOWNLOAD_ID2; 337 mForegroundService.stopDownloadForegroundService( 338 DownloadForegroundServiceImpl.StopForegroundNotification.DETACH, FAKE_DOWNLOAD_ID1, 339 mNotification); 340 expectedMethodCalls = 341 Arrays.asList(MockDownloadForegroundService.MethodID.RELAUNCH_NOTIFICATION, 342 MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS); 343 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 344 assertEquals(ServiceCompat.STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 345 assertEquals(mForegroundService.mNextNotificationId, 346 mForegroundService.mRelaunchedNotificationId); 347 348 // When the service gets stopped with request to not detach but to kill (cancel). 349 mForegroundService.startOrUpdateForegroundService( 350 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 351 mForegroundService.clearStoredState(); 352 353 mForegroundService.stopDownloadForegroundService( 354 DownloadForegroundServiceImpl.StopForegroundNotification.KILL, FAKE_DOWNLOAD_ID1, 355 mNotification); 356 expectedMethodCalls = 357 Arrays.asList(MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS); 358 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 359 assertEquals(ServiceCompat.STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 360 } 361 362 /** 363 * The expected behavior of stop foreground when API < 21 is that the notification is killed in 364 * all cases and relaunched in the pause and complete/failed case. When the notification is 365 * relaunched, it is done so before the foreground is stopped and has a new notification id. 366 */ 367 @Test 368 @SmallTest 369 @Feature({"Download"}) testStopForeground_sdkAtLessThan21()370 public void testStopForeground_sdkAtLessThan21() { 371 // When the service gets stopped with request to detach but not kill notification (pause). 372 mForegroundService.startOrUpdateForegroundService( 373 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 374 mForegroundService.clearStoredState(); 375 376 mForegroundService.mNextNotificationId = FAKE_DOWNLOAD_ID2; 377 mForegroundService.stopDownloadForegroundService( 378 DownloadForegroundServiceImpl.StopForegroundNotification.DETACH, FAKE_DOWNLOAD_ID1, 379 mNotification); 380 List<Integer> expectedMethodCalls = 381 Arrays.asList(MockDownloadForegroundService.MethodID.RELAUNCH_NOTIFICATION, 382 MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS); 383 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 384 assertEquals(ServiceCompat.STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 385 assertEquals(mForegroundService.mNextNotificationId, 386 mForegroundService.mRelaunchedNotificationId); 387 388 // When the service gets stopped with request to detach and kill (complete/failed). 389 mForegroundService.startOrUpdateForegroundService( 390 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 391 mForegroundService.clearStoredState(); 392 393 mForegroundService.mNextNotificationId = FAKE_DOWNLOAD_ID2; 394 mForegroundService.stopDownloadForegroundService( 395 DownloadForegroundServiceImpl.StopForegroundNotification.DETACH, FAKE_DOWNLOAD_ID1, 396 mNotification); 397 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 398 assertEquals(ServiceCompat.STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 399 assertEquals(mForegroundService.mNextNotificationId, 400 mForegroundService.mRelaunchedNotificationId); 401 402 // When the service gets stopped with request to not detach but to kill (cancel). 403 mForegroundService.startOrUpdateForegroundService( 404 FAKE_DOWNLOAD_ID1, mNotification, INVALID_NOTIFICATION_ID, null, false); 405 mForegroundService.clearStoredState(); 406 407 mForegroundService.stopDownloadForegroundService( 408 DownloadForegroundServiceImpl.StopForegroundNotification.KILL, FAKE_DOWNLOAD_ID1, 409 mNotification); 410 expectedMethodCalls = 411 Arrays.asList(MockDownloadForegroundService.MethodID.STOP_FOREGROUND_FLAGS); 412 assertEquals(expectedMethodCalls, mForegroundService.mMethodCalls); 413 assertEquals(ServiceCompat.STOP_FOREGROUND_REMOVE, mForegroundService.mStopForegroundFlags); 414 } 415 } 416