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