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.assertEquals; 8 import static org.junit.Assert.assertFalse; 9 import static org.junit.Assert.assertNotEquals; 10 import static org.junit.Assert.assertNotNull; 11 import static org.junit.Assert.assertNull; 12 import static org.junit.Assert.assertTrue; 13 14 import android.app.Notification; 15 import android.content.Intent; 16 import android.graphics.Bitmap; 17 import android.os.Build; 18 import android.support.v4.media.MediaMetadataCompat; 19 import android.support.v4.media.session.PlaybackStateCompat; 20 21 import org.junit.Test; 22 import org.junit.runner.RunWith; 23 import org.robolectric.RuntimeEnvironment; 24 import org.robolectric.Shadows; 25 import org.robolectric.annotation.Config; 26 import org.robolectric.shadows.ShadowNotification; 27 28 import org.chromium.base.test.BaseRobolectricTestRunner; 29 import org.chromium.chrome.R; 30 import org.chromium.components.browser_ui.media.MediaNotificationController; 31 import org.chromium.components.browser_ui.media.MediaNotificationInfo; 32 import org.chromium.services.media_session.MediaMetadata; 33 import org.chromium.services.media_session.MediaPosition; 34 35 /** 36 * JUnit tests for checking MediaNotificationManager presents correct notification to Android 37 * NotificationManager. 38 */ 39 @RunWith(BaseRobolectricTestRunner.class) 40 @Config(manifest = Config.NONE, 41 // Remove this after updating to a version of Robolectric that supports 42 // notification channel creation. crbug.com/774315 43 sdk = Build.VERSION_CODES.N_MR1, shadows = MediaNotificationTestShadowResources.class) 44 public class MediaNotificationManagerNotificationTest extends MediaNotificationTestBase { 45 @Test updateNotificationBuilderDisplaysCorrectMetadata_PreN_NonEmptyArtistAndAlbum()46 public void updateNotificationBuilderDisplaysCorrectMetadata_PreN_NonEmptyArtistAndAlbum() { 47 MediaNotificationController.sOverrideIsRunningNForTesting = false; 48 49 mMediaNotificationInfoBuilder.setMetadata(new MediaMetadata("title", "artist", "album")); 50 mMediaNotificationInfoBuilder.setOrigin("https://example.com/"); 51 52 MediaNotificationInfo info = mMediaNotificationInfoBuilder.build(); 53 Notification notification = updateNotificationBuilderAndBuild(info); 54 55 ShadowNotification shadowNotification = Shadows.shadowOf(notification); 56 57 if (info.isPrivate) { 58 assertNotEquals("title", shadowNotification.getContentTitle()); 59 assertNotEquals("artist - album", shadowNotification.getContentText()); 60 if (hasNApis()) { 61 assertNull(notification.extras.getString(Notification.EXTRA_SUB_TEXT)); 62 } 63 } else { 64 assertEquals("title", shadowNotification.getContentTitle()); 65 assertEquals("artist - album", shadowNotification.getContentText()); 66 67 if (hasNApis()) { 68 assertEquals("https://example.com/", 69 notification.extras.getString(Notification.EXTRA_SUB_TEXT)); 70 } 71 } 72 } 73 74 @Test updateNotificationBuilderDisplaysCorrectMetadata_PreN_EmptyArtistAndAlbum()75 public void updateNotificationBuilderDisplaysCorrectMetadata_PreN_EmptyArtistAndAlbum() { 76 MediaNotificationController.sOverrideIsRunningNForTesting = false; 77 78 mMediaNotificationInfoBuilder.setMetadata(new MediaMetadata("title", "", "")); 79 mMediaNotificationInfoBuilder.setOrigin("https://example.com/"); 80 81 MediaNotificationInfo info = mMediaNotificationInfoBuilder.build(); 82 Notification notification = updateNotificationBuilderAndBuild(info); 83 84 ShadowNotification shadowNotification = Shadows.shadowOf(notification); 85 86 if (info.isPrivate) { 87 assertNotEquals(info.metadata.getTitle(), shadowNotification.getContentTitle()); 88 assertNotNull(shadowNotification.getContentText()); 89 } else { 90 assertEquals(info.metadata.getTitle(), shadowNotification.getContentTitle()); 91 assertEquals(info.origin, shadowNotification.getContentText()); 92 } 93 if (hasNApis()) { 94 assertEquals(null, notification.extras.getString(Notification.EXTRA_SUB_TEXT)); 95 } 96 } 97 98 @Test updateNotificationBuilderDisplaysCorrectMetadata_AtLeastN_EmptyArtistAndAlbum()99 public void updateNotificationBuilderDisplaysCorrectMetadata_AtLeastN_EmptyArtistAndAlbum() { 100 MediaNotificationController.sOverrideIsRunningNForTesting = true; 101 102 mMediaNotificationInfoBuilder.setMetadata(new MediaMetadata("title", "", "")); 103 mMediaNotificationInfoBuilder.setOrigin("https://example.com/"); 104 105 MediaNotificationInfo info = mMediaNotificationInfoBuilder.build(); 106 Notification notification = updateNotificationBuilderAndBuild(info); 107 108 ShadowNotification shadowNotification = Shadows.shadowOf(notification); 109 110 if (info.isPrivate) { 111 assertNotEquals(info.metadata.getTitle(), shadowNotification.getContentTitle()); 112 assertNull(shadowNotification.getContentText()); 113 if (hasNApis()) { 114 assertNotEquals( 115 info.origin, notification.extras.getString(Notification.EXTRA_SUB_TEXT)); 116 } 117 } else { 118 assertEquals(info.metadata.getTitle(), shadowNotification.getContentTitle()); 119 assertEquals("", shadowNotification.getContentText()); 120 if (hasNApis()) { 121 assertEquals( 122 info.origin, notification.extras.getString(Notification.EXTRA_SUB_TEXT)); 123 } 124 } 125 } 126 127 @Test updateNotificationBuilderDisplaysCorrectLargeIcon_WithLargeIcon()128 public void updateNotificationBuilderDisplaysCorrectLargeIcon_WithLargeIcon() { 129 Bitmap largeIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 130 mMediaNotificationInfoBuilder.setNotificationLargeIcon(largeIcon); 131 132 MediaNotificationInfo info = mMediaNotificationInfoBuilder.build(); 133 Notification notification = updateNotificationBuilderAndBuild(info); 134 135 if (hasNApis()) { 136 if (info.isPrivate) { 137 assertNull(notification.getLargeIcon()); 138 } else { 139 assertTrue(largeIcon.sameAs(iconToBitmap(notification.getLargeIcon()))); 140 } 141 } 142 } 143 144 @Test updateNotificationBuilderDisplaysCorrectLargeIcon_WithoutLargeIcon_AtLeastN()145 public void updateNotificationBuilderDisplaysCorrectLargeIcon_WithoutLargeIcon_AtLeastN() { 146 mMediaNotificationInfoBuilder.setNotificationLargeIcon(null); 147 148 MediaNotificationInfo info = mMediaNotificationInfoBuilder.build(); 149 Notification notification = updateNotificationBuilderAndBuild(info); 150 151 if (hasNApis()) { 152 assertNull(notification.getLargeIcon()); 153 } 154 assertNull(getController().mDefaultNotificationLargeIcon); 155 } 156 157 @Test updateNotificationBuilderDisplaysCorrectLargeIcon_WithoutLargeIcon_PreN()158 public void updateNotificationBuilderDisplaysCorrectLargeIcon_WithoutLargeIcon_PreN() { 159 MediaNotificationController.sOverrideIsRunningNForTesting = false; 160 assertNull(getController().mDefaultNotificationLargeIcon); 161 162 mMediaNotificationInfoBuilder.setNotificationLargeIcon(null); 163 164 MediaNotificationInfo info = 165 mMediaNotificationInfoBuilder 166 .setDefaultNotificationLargeIcon(R.drawable.audio_playing_square) 167 .build(); 168 Notification notification = updateNotificationBuilderAndBuild(info); 169 170 assertNotNull(getController().mDefaultNotificationLargeIcon); 171 if (hasNApis()) { 172 assertTrue(getController().mDefaultNotificationLargeIcon.sameAs( 173 iconToBitmap(notification.getLargeIcon()))); 174 } 175 } 176 177 @Test updateNotificationBuilderDisplaysCorrectLargeIcon_DontSupportPlayPause()178 public void updateNotificationBuilderDisplaysCorrectLargeIcon_DontSupportPlayPause() { 179 Bitmap largeIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 180 mMediaNotificationInfoBuilder.setNotificationLargeIcon(largeIcon).setActions(0); 181 182 MediaNotificationInfo info = mMediaNotificationInfoBuilder.build(); 183 Notification notification = updateNotificationBuilderAndBuild(info); 184 185 if (hasNApis()) { 186 assertNull(notification.getLargeIcon()); 187 } 188 } 189 190 @Test updateNotificationBuilderDisplaysCorrectMiscInfo()191 public void updateNotificationBuilderDisplaysCorrectMiscInfo() { 192 mMediaNotificationInfoBuilder.setNotificationSmallIcon(1 /* resId */) 193 .setActions(0) 194 .setContentIntent(new Intent()); 195 MediaNotificationInfo info = mMediaNotificationInfoBuilder.build(); 196 Notification notification = updateNotificationBuilderAndBuild(info); 197 198 ShadowNotification shadowNotification = Shadows.shadowOf(notification); 199 200 assertFalse(shadowNotification.isWhenShown()); 201 assertFalse(shadowNotification.isOngoing()); 202 if (hasNApis()) { 203 assertNotNull(notification.getSmallIcon()); 204 assertFalse((notification.flags & Notification.FLAG_AUTO_CANCEL) != 0); 205 assertTrue((notification.flags & Notification.FLAG_LOCAL_ONLY) != 0); 206 assertEquals(NOTIFICATION_GROUP_NAME, notification.getGroup()); 207 assertTrue(notification.isGroupSummary()); 208 assertNotNull(notification.contentIntent); 209 assertEquals(Notification.VISIBILITY_PRIVATE, notification.visibility); 210 } 211 } 212 213 @Test updateNotificationBuilderDisplaysCorrectMiscInfo_SupportsSwipeAway()214 public void updateNotificationBuilderDisplaysCorrectMiscInfo_SupportsSwipeAway() { 215 MediaNotificationInfo info = mMediaNotificationInfoBuilder.build(); 216 Notification notification = updateNotificationBuilderAndBuild(info); 217 218 ShadowNotification shadowNotification = Shadows.shadowOf(notification); 219 220 assertTrue(shadowNotification.isOngoing()); 221 if (hasNApis()) { 222 assertNotNull(notification.deleteIntent); 223 } 224 } 225 226 @Test updateNotificationBuilderDisplaysCorrectMiscInfo_Private()227 public void updateNotificationBuilderDisplaysCorrectMiscInfo_Private() { 228 mMediaNotificationInfoBuilder.setPrivate(false); 229 MediaNotificationInfo info = mMediaNotificationInfoBuilder.build(); 230 Notification notification = updateNotificationBuilderAndBuild(info); 231 232 if (hasNApis()) { 233 assertEquals(Notification.VISIBILITY_PUBLIC, notification.visibility); 234 } 235 } 236 237 @Test mediaPosition_Present()238 public void mediaPosition_Present() { 239 MediaPosition position = new MediaPosition(10, 5, 2.0f, 10000); 240 mMediaNotificationInfoBuilder.setPaused(false); 241 mMediaNotificationInfoBuilder.setMediaPosition(position); 242 mMediaNotificationInfoBuilder.setPrivate(false); 243 getController().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build(); 244 245 MediaMetadataCompat metadata = getController().createMetadata(); 246 assertEquals(10, metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)); 247 248 PlaybackStateCompat state = getController().createPlaybackState(); 249 assertEquals(PlaybackStateCompat.STATE_PLAYING, state.getState()); 250 assertEquals(2.0f, state.getPlaybackSpeed(), 0); 251 assertEquals(5, state.getPosition()); 252 assertEquals(10000, state.getLastPositionUpdateTime()); 253 } 254 255 @Test mediaPosition_Present_Paused()256 public void mediaPosition_Present_Paused() { 257 MediaPosition position = new MediaPosition(10, 5, 2.0f, 10000); 258 mMediaNotificationInfoBuilder.setPaused(true); 259 mMediaNotificationInfoBuilder.setMediaPosition(position); 260 mMediaNotificationInfoBuilder.setPrivate(false); 261 getController().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build(); 262 263 MediaMetadataCompat metadata = getController().createMetadata(); 264 assertEquals(10, metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)); 265 266 PlaybackStateCompat state = getController().createPlaybackState(); 267 assertEquals(PlaybackStateCompat.STATE_PAUSED, state.getState()); 268 assertEquals(2.0f, state.getPlaybackSpeed(), 0); 269 assertEquals(5, state.getPosition()); 270 assertEquals(10000, state.getLastPositionUpdateTime()); 271 } 272 273 @Test mediaPosition_Missing()274 public void mediaPosition_Missing() { 275 mMediaNotificationInfoBuilder.setPaused(false); 276 mMediaNotificationInfoBuilder.setPrivate(false); 277 getController().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build(); 278 279 MediaMetadataCompat metadata = getController().createMetadata(); 280 assertFalse(metadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)); 281 282 PlaybackStateCompat state = getController().createPlaybackState(); 283 assertEquals(PlaybackStateCompat.STATE_PLAYING, state.getState()); 284 assertEquals(1.0f, state.getPlaybackSpeed(), 0); 285 assertEquals(PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, state.getPosition()); 286 } 287 288 @Test mediaPosition_Missing_Paused()289 public void mediaPosition_Missing_Paused() { 290 mMediaNotificationInfoBuilder.setPaused(true); 291 mMediaNotificationInfoBuilder.setPrivate(false); 292 getController().mMediaNotificationInfo = mMediaNotificationInfoBuilder.build(); 293 294 MediaMetadataCompat metadata = getController().createMetadata(); 295 assertFalse(metadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)); 296 297 PlaybackStateCompat state = getController().createPlaybackState(); 298 assertEquals(PlaybackStateCompat.STATE_PAUSED, state.getState()); 299 assertEquals(1.0f, state.getPlaybackSpeed(), 0); 300 assertEquals(PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, state.getPosition()); 301 } 302 updateNotificationBuilderAndBuild(MediaNotificationInfo info)303 private Notification updateNotificationBuilderAndBuild(MediaNotificationInfo info) { 304 getController().mMediaNotificationInfo = info; 305 306 // This is the fake implementation to ensure |mMediaSession| is non-null. 307 // 308 // TODO(zqzhang): move around code so that updateNotification() doesn't need a MediaSession. 309 getController().updateMediaSession(); 310 getController().updateNotificationBuilder(); 311 312 return getController().mNotificationBuilder.build(); 313 } 314 hasNApis()315 private boolean hasNApis() { 316 return RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.N; 317 } 318 } 319