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