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.media.ui;
6 
7 import static org.junit.Assert.assertTrue;
8 import static org.mockito.Mockito.any;
9 import static org.mockito.Mockito.clearInvocations;
10 import static org.mockito.Mockito.doAnswer;
11 import static org.mockito.Mockito.doNothing;
12 import static org.mockito.Mockito.doReturn;
13 import static org.mockito.Mockito.mock;
14 import static org.mockito.Mockito.spy;
15 
16 import android.content.ComponentName;
17 import android.content.Context;
18 import android.content.Intent;
19 import android.graphics.Bitmap;
20 import android.graphics.drawable.BitmapDrawable;
21 import android.graphics.drawable.Icon;
22 import android.os.Build;
23 import android.support.v4.media.session.MediaSessionCompat;
24 
25 import org.junit.After;
26 import org.junit.Before;
27 import org.junit.Rule;
28 import org.mockito.invocation.InvocationOnMock;
29 import org.mockito.stubbing.Answer;
30 import org.robolectric.RuntimeEnvironment;
31 import org.robolectric.shadows.ShadowLog;
32 import org.robolectric.shadows.ShadowLooper;
33 
34 import org.chromium.base.ContextUtils;
35 import org.chromium.base.test.util.JniMocker;
36 import org.chromium.chrome.browser.AppHooks;
37 import org.chromium.chrome.browser.base.SplitCompatService;
38 import org.chromium.chrome.browser.media.ui.ChromeMediaNotificationControllerDelegate.ListenerServiceImpl;
39 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
40 import org.chromium.components.browser_ui.media.MediaNotificationController;
41 import org.chromium.components.browser_ui.media.MediaNotificationInfo;
42 import org.chromium.components.browser_ui.media.MediaNotificationListener;
43 import org.chromium.components.browser_ui.media.MediaNotificationManager;
44 import org.chromium.components.browser_ui.notifications.ForegroundServiceUtils;
45 import org.chromium.components.browser_ui.notifications.NotificationWrapper;
46 import org.chromium.media_session.mojom.MediaSessionAction;
47 import org.chromium.services.media_session.MediaMetadata;
48 
49 import java.util.Set;
50 import java.util.concurrent.TimeUnit;
51 import java.util.stream.Collectors;
52 import java.util.stream.Stream;
53 
54 /**
55  * Common test fixtures for MediaNotificationController JUnit tests.
56  */
57 public class MediaNotificationTestBase {
58     private static final int NOTIFICATION_ID = 0;
59     private static final int TAB_ID = 0;
60     static final String NOTIFICATION_GROUP_NAME = "group-name";
61     static final Set<Integer> DEFAULT_ACTIONS =
62             Stream.of(MediaSessionAction.PLAY).collect(Collectors.toSet());
63     Context mMockContext;
64     MockListenerService mService;
65     MediaNotificationListener mListener;
66     ForegroundServiceUtils mMockForegroundServiceUtils;
67     NotificationUmaTracker mMockUmaTracker;
68 
69     MediaNotificationInfo.Builder mMediaNotificationInfoBuilder;
70 
71     @Rule
72     public JniMocker mocker = new JniMocker();
73 
createMediaNotificationTestTabHolder( int tabId, String url, String title)74     protected MediaNotificationTestTabHolder createMediaNotificationTestTabHolder(
75             int tabId, String url, String title) {
76         return new MediaNotificationTestTabHolder(tabId, url, title, mocker);
77     }
78 
79     static class MockMediaNotificationController extends MediaNotificationController {
MockMediaNotificationController(MediaNotificationController.Delegate delegate)80         public MockMediaNotificationController(MediaNotificationController.Delegate delegate) {
81             super(delegate);
82         }
83     }
84 
85     class MockListenerServiceImpl extends ListenerServiceImpl {
MockListenerServiceImpl()86         MockListenerServiceImpl() {
87             super(MediaNotificationTestBase.this.getNotificationId());
88         }
89 
setServiceForTesting(SplitCompatService service)90         public void setServiceForTesting(SplitCompatService service) {
91             setService(service);
92         }
93     }
94 
95     class MockListenerService extends SplitCompatService {
96         private MockListenerServiceImpl mImpl;
97 
MockListenerService()98         MockListenerService() {
99             super(null);
100             mImpl = spy(new MockListenerServiceImpl());
101             attachBaseContextForTesting(null, mImpl);
102         }
103 
getImpl()104         MockListenerServiceImpl getImpl() {
105             return mImpl;
106         }
107     }
108 
109     @Before
setUp()110     public void setUp() {
111         // For checking the notification presented to NotificationManager.
112         assertTrue(RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.N);
113 
114         ShadowLog.stream = System.out;
115 
116         mMockContext = spy(RuntimeEnvironment.application);
117         ContextUtils.initApplicationContextForTests(mMockContext);
118 
119         mListener = mock(MediaNotificationListener.class);
120 
121         ChromeMediaNotificationControllerDelegate.sMapNotificationIdToOptions.put(
122                 getNotificationId(),
123                 new ChromeMediaNotificationControllerDelegate.NotificationOptions(
124                         MockListenerService.class, NOTIFICATION_GROUP_NAME));
125 
126         mMockUmaTracker = mock(NotificationUmaTracker.class);
127         MediaNotificationManager.setControllerForTesting(getNotificationId(),
128                 spy(new MockMediaNotificationController(
129                         new ChromeMediaNotificationControllerDelegate(getNotificationId()) {
130                             @Override
131                             public void logNotificationShown(NotificationWrapper notification) {
132                                 mMockUmaTracker.onNotificationShown(
133                                         NotificationUmaTracker.SystemNotificationType.MEDIA,
134                                         notification.getNotification());
135                             }
136                         })));
137 
138         mMediaNotificationInfoBuilder =
139                 new MediaNotificationInfo.Builder()
140                         .setMetadata(new MediaMetadata("title", "artist", "album"))
141                         .setOrigin("https://example.com")
142                         .setListener(mListener)
143                         .setMediaSessionActions(DEFAULT_ACTIONS)
144                         .setId(getNotificationId())
145                         .setInstanceId(TAB_ID);
146 
147         doNothing().when(getController()).onServiceStarted(any(MockListenerService.class));
148         // Robolectric does not have "ShadowMediaSession".
149         doAnswer(new Answer() {
150             @Override
151             public Object answer(InvocationOnMock invocation) {
152                 MediaSessionCompat mockSession = mock(MediaSessionCompat.class);
153                 getController().mMediaSession = mockSession;
154                 doReturn(null).when(mockSession).getSessionToken();
155                 return "Created mock media session";
156             }
157         })
158                 .when(getController())
159                 .updateMediaSession();
160 
161         doAnswer(new Answer() {
162             @Override
163             public Object answer(InvocationOnMock invocation) {
164                 Intent intent = (Intent) invocation.getArgument(0);
165                 startService(intent);
166                 return new ComponentName(mMockContext, MockListenerService.class);
167             }
168         })
169                 .when(mMockContext)
170                 .startService(any(Intent.class));
171 
172         mMockForegroundServiceUtils = mock(ForegroundServiceUtils.class);
173         ForegroundServiceUtils.setInstanceForTesting(mMockForegroundServiceUtils);
174     }
175 
176     @After
tearDown()177     public void tearDown() {
178         AppHooks.setInstanceForTesting(null);
179         MediaNotificationManager.clear(NOTIFICATION_ID);
180     }
181 
getController()182     MediaNotificationController getController() {
183         return MediaNotificationManager.getController(getNotificationId());
184     }
185 
ensureMediaNotificationInfo()186     void ensureMediaNotificationInfo() {
187         getController().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build();
188     }
189 
setUpServiceAndClearInvocations()190     void setUpServiceAndClearInvocations() {
191         setUpService();
192         clearInvocations(getController());
193         clearInvocations(mService);
194         clearInvocations(mMockContext);
195         clearInvocations(mMockUmaTracker);
196     }
197 
setUpService()198     void setUpService() {
199         ensureMediaNotificationInfo();
200 
201         Intent intent = getController().mDelegate.createServiceIntent();
202         mMockContext.startService(intent);
203     }
204 
startService(Intent intent)205     private void startService(Intent intent) {
206         ensureService();
207         mService.onStartCommand(intent, 0, 0);
208     }
209 
ensureService()210     private void ensureService() {
211         if (mService != null) return;
212         mService = spy(new MockListenerService());
213         MockListenerServiceImpl impl = mService.getImpl();
214         impl.setServiceForTesting(mService);
215 
216         doAnswer(new Answer() {
217             @Override
218             public Object answer(InvocationOnMock invocation) {
219                 mService.onDestroy();
220                 mService = null;
221                 return "service stopped";
222             }
223         })
224                 .when(impl)
225                 .stopListenerService();
226     }
227 
iconToBitmap(Icon icon)228     Bitmap iconToBitmap(Icon icon) {
229         if (icon == null) return null;
230 
231         BitmapDrawable drawable = (BitmapDrawable) icon.loadDrawable(mMockContext);
232         assert drawable != null;
233         return drawable.getBitmap();
234     }
235 
getNotificationId()236     int getNotificationId() {
237         return NOTIFICATION_ID;
238     }
239 
advanceTimeByMillis(int timeMillis)240     void advanceTimeByMillis(int timeMillis) {
241         ShadowLooper.idleMainLooper(timeMillis, TimeUnit.MILLISECONDS);
242     }
243 }
244