@@ -13,6 +13,7 @@ import 'package:http/testing.dart' as http_testing;
13
13
import 'package:zulip/api/model/model.dart' ;
14
14
import 'package:zulip/api/notifications.dart' ;
15
15
import 'package:zulip/host/android_notifications.dart' ;
16
+ import 'package:zulip/model/binding.dart' ;
16
17
import 'package:zulip/model/localizations.dart' ;
17
18
import 'package:zulip/model/narrow.dart' ;
18
19
import 'package:zulip/model/store.dart' ;
@@ -121,15 +122,16 @@ void main() {
121
122
await NotificationService .instance.start ();
122
123
}
123
124
124
- group ('NotificationChannelManager' , () {
125
+ group ('NotificationChannelManager create channel ' , () {
125
126
test ('smoke' , () async {
126
127
await init ();
127
128
check (testBinding.androidNotificationHost.takeCreatedChannels ()).single
128
129
..id.equals (NotificationChannelManager .kChannelId)
129
130
..name.equals ('Messages' )
130
131
..importance.equals (NotificationImportance .high)
131
132
..lightsEnabled.equals (true )
132
- ..soundUrl.isNull ()
133
+ ..soundUrl.equals (testBinding.androidNotificationHost.fakeStoredNotificationSoundUrl (
134
+ NotificationChannelManager .kDefaultNotificationSound.resourceName))
133
135
..vibrationPattern.isNotNull ().deepEquals (
134
136
NotificationChannelManager .kVibrationPattern)
135
137
;
@@ -211,6 +213,134 @@ void main() {
211
213
});
212
214
});
213
215
216
+ group ('NotificationChannelManager sounds' , () {
217
+ String fakeStoredUrl (String resourceName) =>
218
+ testBinding.androidNotificationHost.fakeStoredNotificationSoundUrl (resourceName);
219
+
220
+ test ('on Android 28 (and lower) resource file is used for notification sound' , () async {
221
+ addTearDown (testBinding.reset);
222
+ final androidNotificationHost = testBinding.androidNotificationHost;
223
+
224
+ testBinding.deviceInfoResult =
225
+ const AndroidDeviceInfo (sdkInt: 28 , release: '9' );
226
+
227
+ // Ensure that on Android 10, notification sounds aren't being copied to
228
+ // the media store, and resource file is used directly.
229
+ await NotificationChannelManager .ensureChannel ();
230
+ check (androidNotificationHost.takeCopySoundResourceToMediaStoreCalls ())
231
+ .isEmpty ();
232
+
233
+ final defaultSoundResourceName =
234
+ NotificationChannelManager .kDefaultNotificationSound.resourceName;
235
+ final soundUrl =
236
+ 'android.resource://com.zulip.flutter/raw/$defaultSoundResourceName ' ;
237
+ check (androidNotificationHost.takeCreatedChannels ())
238
+ .single
239
+ .soundUrl.equals (soundUrl);
240
+ });
241
+
242
+ test ('notification sound resource files are being copied to the media store' , () async {
243
+ addTearDown (testBinding.reset);
244
+ final androidNotificationHost = testBinding.androidNotificationHost;
245
+
246
+ await NotificationChannelManager .ensureChannel ();
247
+ check (androidNotificationHost.takeCopySoundResourceToMediaStoreCalls ())
248
+ .deepEquals (NotificationSound .values.map ((e) => (
249
+ sourceResourceName: e.resourceName,
250
+ targetFileDisplayName: e.fileDisplayName),
251
+ ));
252
+
253
+ // Ensure the default source URL points to a file in the media store,
254
+ // rather than a resource file.
255
+ final defaultSoundResourceName =
256
+ NotificationChannelManager .kDefaultNotificationSound.resourceName;
257
+ check (androidNotificationHost.takeCreatedChannels ())
258
+ .single
259
+ .soundUrl.equals (fakeStoredUrl (defaultSoundResourceName));
260
+ });
261
+
262
+ test ('notification sounds are not copied again if they were previously copied' , () async {
263
+ addTearDown (testBinding.reset);
264
+ final androidNotificationHost = testBinding.androidNotificationHost;
265
+
266
+ // Emulate that all notifications sounds are already in the media store.
267
+ androidNotificationHost.setupStoredNotificationSounds (
268
+ NotificationSound .values.map ((e) => StoredNotificationSound (
269
+ fileName: e.fileDisplayName,
270
+ isOwned: true ,
271
+ contentUrl: fakeStoredUrl (e.resourceName)),
272
+ ).toList (),
273
+ );
274
+
275
+ await NotificationChannelManager .ensureChannel ();
276
+ check (androidNotificationHost.takeCopySoundResourceToMediaStoreCalls ())
277
+ .isEmpty ();
278
+
279
+ final defaultSoundResourceName =
280
+ NotificationChannelManager .kDefaultNotificationSound.resourceName;
281
+ check (androidNotificationHost.takeCreatedChannels ())
282
+ .single
283
+ .soundUrl.equals (fakeStoredUrl (defaultSoundResourceName));
284
+ });
285
+
286
+ test ('new notification sounds are copied to media store' , () async {
287
+ addTearDown (testBinding.reset);
288
+ final androidNotificationHost = testBinding.androidNotificationHost;
289
+
290
+ // Emulate that except one sound, all other sounds are already in
291
+ // media store.
292
+ androidNotificationHost.setupStoredNotificationSounds (
293
+ NotificationSound .values.skip (1 ).map ((e) => StoredNotificationSound (
294
+ fileName: e.fileDisplayName,
295
+ isOwned: true ,
296
+ contentUrl: fakeStoredUrl (e.resourceName)),
297
+ ).toList ()
298
+ );
299
+
300
+ await NotificationChannelManager .ensureChannel ();
301
+ final firstSound = NotificationSound .values.first;
302
+ check (androidNotificationHost.takeCopySoundResourceToMediaStoreCalls ())
303
+ .single
304
+ ..sourceResourceName.equals (firstSound.resourceName)
305
+ ..targetFileDisplayName.equals (firstSound.fileDisplayName);
306
+
307
+ final defaultSoundResourceName =
308
+ NotificationChannelManager .kDefaultNotificationSound.resourceName;
309
+ check (androidNotificationHost.takeCreatedChannels ())
310
+ .single
311
+ .soundUrl.equals (fakeStoredUrl (defaultSoundResourceName));
312
+ });
313
+
314
+ test ('no recopying of existing notification sounds in the media store; default sound URL points to resource file' , () async {
315
+ addTearDown (testBinding.reset);
316
+ final androidNotificationHost = testBinding.androidNotificationHost;
317
+
318
+ androidNotificationHost.setupStoredNotificationSounds (
319
+ NotificationSound .values.map ((e) => StoredNotificationSound (
320
+ fileName: e.fileDisplayName,
321
+ isOwned: false ,
322
+ contentUrl: fakeStoredUrl (e.resourceName)),
323
+ ).toList ()
324
+ );
325
+
326
+ // Ensure that if a notification sound with the same name already exists
327
+ // in the media store, but it wasn't copied by us, no recopying should
328
+ // happen. Additionally, the default sound URL should point to the
329
+ // resource file, not the version in the media store.
330
+ await NotificationChannelManager .ensureChannel ();
331
+ check (androidNotificationHost.takeCopySoundResourceToMediaStoreCalls ())
332
+ .isEmpty ();
333
+
334
+ final defaultSoundResourceName =
335
+ NotificationChannelManager .kDefaultNotificationSound.resourceName;
336
+ final soundUrl =
337
+ 'android.resource://com.zulip.flutter/raw/$defaultSoundResourceName ' ;
338
+ check (androidNotificationHost.takeCreatedChannels ())
339
+ .single
340
+ .soundUrl.equals (soundUrl);
341
+ });
342
+ });
343
+
214
344
group ('NotificationDisplayManager show' , () {
215
345
void checkNotification (MessageFcmMessage data, {
216
346
required List <MessageFcmMessage > messageStyleMessages,
@@ -1182,6 +1312,11 @@ void main() {
1182
1312
});
1183
1313
}
1184
1314
1315
+ extension on Subject <CopySoundResourceToMediaStoreCall > {
1316
+ Subject <String > get targetFileDisplayName => has ((x) => x.targetFileDisplayName, 'targetFileDisplayName' );
1317
+ Subject <String > get sourceResourceName => has ((x) => x.sourceResourceName, 'sourceResourceName' );
1318
+ }
1319
+
1185
1320
extension NotificationChannelChecks on Subject <NotificationChannel > {
1186
1321
Subject <String > get id => has ((x) => x.id, 'id' );
1187
1322
Subject <int > get importance => has ((x) => x.importance, 'importance' );
0 commit comments