diff --git a/lib/notifications/display.dart b/lib/notifications/display.dart index 2e8288bdc4..8c90b381b0 100644 --- a/lib/notifications/display.dart +++ b/lib/notifications/display.dart @@ -25,6 +25,10 @@ AndroidNotificationHostApi get _androidHost => ZulipBinding.instance.androidNoti /// Service for configuring our Android "notification channel". class NotificationChannelManager { + /// The channel ID we use for our one notification channel, which we use for + /// all notifications. + // TODO(launch) check this doesn't match zulip-mobile's current or previous + // channel IDs @visibleForTesting static const kChannelId = 'messages-1'; @@ -36,6 +40,8 @@ class NotificationChannelManager { static final kVibrationPattern = Int64List.fromList([0, 125, 100, 450]); /// Create our notification channel, if it doesn't already exist. + /// + /// Deletes obsolete channels, if present, from old versions of the app. // // NOTE when changing anything here: the changes will not take effect // for existing installs of the app! That's because we'll have already @@ -52,11 +58,28 @@ class NotificationChannelManager { // settings for the channel -- like "override Do Not Disturb", or "use // a different sound", or "don't pop on screen" -- their changes get // reset. So this has to be done sparingly. - // - // If we do this, we should also look for any channel with the old - // channel ID and delete it. See zulip-mobile's `createNotificationChannel` - // in android/app/src/main/java/com/zulipmobile/notifications/NotificationChannelManager.kt . - static Future _ensureChannel() async { + @visibleForTesting + static Future ensureChannel() async { + // See if our current-version channel already exists; delete any obsolete + // previous channels. + var found = false; + final channels = await _androidHost.getNotificationChannels(); + for (final channel in channels) { + assert(channel != null); // TODO(#942) + if (channel!.id == kChannelId) { + found = true; + } else { + await _androidHost.deleteNotificationChannel(channel.id); + } + } + + if (found) { + // The channel already exists; nothing to do. + return; + } + + // The channel doesn't exist. Create it. + await _androidHost.createNotificationChannel(NotificationChannel( id: kChannelId, name: 'Messages', // TODO(i18n) @@ -81,7 +104,7 @@ class NotificationDisplayManager { if (launchDetails?.didNotificationLaunchApp ?? false) { _handleNotificationAppLaunch(launchDetails!.notificationResponse); } - await NotificationChannelManager._ensureChannel(); + await NotificationChannelManager.ensureChannel(); } static void onFcmMessage(FcmMessage data, Map dataJson) { diff --git a/test/model/binding.dart b/test/model/binding.dart index 23a6cba89d..9a29168a9b 100644 --- a/test/model/binding.dart +++ b/test/model/binding.dart @@ -554,20 +554,32 @@ class FakeAndroidNotificationHostApi implements AndroidNotificationHostApi { } List _createdChannels = []; + /// Consumes the log of calls made to [getNotificationChannels], + /// [deleteNotificationChannel] and [createNotificationChannel]. + /// + /// Returns a list of function names in the order they were invoked. + List takeChannelMethodCallLogs() { + final result = _channelMethodCallLogs; + _channelMethodCallLogs = []; + return result; + } + List _channelMethodCallLogs = []; + @override - Future> getNotificationChannels() { - // TODO: implement getNotificationChannels - throw UnimplementedError(); + Future> getNotificationChannels() async { + _channelMethodCallLogs.add('getNotificationChannels'); + return _createdChannels.toList(growable: false); } @override - Future deleteNotificationChannel(String channelId) { - // TODO: implement deleteNotificationChannel - throw UnimplementedError(); + Future deleteNotificationChannel(String channelId) async { + _channelMethodCallLogs.add('deleteNotificationChannel'); + _createdChannels.removeWhere((e) => e.id == channelId); } @override Future createNotificationChannel(NotificationChannel channel) async { + _channelMethodCallLogs.add('createNotificationChannel'); _createdChannels.add(channel); } diff --git a/test/notifications/display_test.dart b/test/notifications/display_test.dart index dbaef45f9f..bf107f4823 100644 --- a/test/notifications/display_test.dart +++ b/test/notifications/display_test.dart @@ -131,6 +131,59 @@ void main() { NotificationChannelManager.kVibrationPattern) ; }); + + test('channel is not recreated if one with same id already exists', () async { + await NotificationChannelManager.ensureChannel(); + check(testBinding.androidNotificationHost.takeChannelMethodCallLogs()) + .deepEquals(['getNotificationChannels', 'createNotificationChannel']); + + await NotificationChannelManager.ensureChannel(); + check(testBinding.androidNotificationHost.takeChannelMethodCallLogs()) + .deepEquals(['getNotificationChannels']); + + check(testBinding.androidNotificationHost.takeCreatedChannels()).single + ..id.equals(NotificationChannelManager.kChannelId) + ..name.equals('Messages') + ..importance.equals(NotificationImportance.high) + ..lightsEnabled.equals(true) + ..vibrationPattern.isNotNull().deepEquals( + NotificationChannelManager.kVibrationPattern); + }); + + test('obsolete channels are removed', () async { + await NotificationChannelManager.ensureChannel(); + check(testBinding.androidNotificationHost.takeChannelMethodCallLogs()) + .deepEquals(['getNotificationChannels', 'createNotificationChannel']); + + await testBinding.androidNotificationHost.createNotificationChannel(NotificationChannel( + id: 'obsolete-1', + name: 'Obsolete 1', + importance: NotificationImportance.high, + lightsEnabled: true, + vibrationPattern: NotificationChannelManager.kVibrationPattern)); + await testBinding.androidNotificationHost.createNotificationChannel(NotificationChannel( + id: 'obsolete-2', + name: 'Obsolete 2', + importance: NotificationImportance.high, + lightsEnabled: true, + vibrationPattern: NotificationChannelManager.kVibrationPattern)); + check(testBinding.androidNotificationHost.takeChannelMethodCallLogs()) + .deepEquals(['createNotificationChannel', 'createNotificationChannel']); + + await NotificationChannelManager.ensureChannel(); + check(testBinding.androidNotificationHost.takeChannelMethodCallLogs()) + .deepEquals([ + 'getNotificationChannels', + 'deleteNotificationChannel', + 'deleteNotificationChannel']); + check(testBinding.androidNotificationHost.takeCreatedChannels()).single + ..id.equals(NotificationChannelManager.kChannelId) + ..name.equals('Messages') + ..importance.equals(NotificationImportance.high) + ..lightsEnabled.equals(true) + ..vibrationPattern.isNotNull().deepEquals( + NotificationChannelManager.kVibrationPattern); + }); }); group('NotificationDisplayManager show', () {