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