Skip to content

Commit b3a5b2a

Browse files
notif: Support migration of Android notification channels
Needed for #340, when updating notification channels to use a custom notification sound.
1 parent 5ea12f1 commit b3a5b2a

File tree

3 files changed

+100
-12
lines changed

3 files changed

+100
-12
lines changed

lib/notifications/display.dart

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ AndroidNotificationHostApi get _androidHost => ZulipBinding.instance.androidNoti
2525

2626
/// Service for configuring our Android "notification channel".
2727
class NotificationChannelManager {
28+
/// The channel ID we use for our one notification channel, which we use for
29+
/// all notifications.
30+
// TODO(launch) check this doesn't match zulip-mobile's current or previous
31+
// channel IDs
2832
@visibleForTesting
2933
static const kChannelId = 'messages-1';
3034

@@ -36,6 +40,8 @@ class NotificationChannelManager {
3640
static final kVibrationPattern = Int64List.fromList([0, 125, 100, 450]);
3741

3842
/// Create our notification channel, if it doesn't already exist.
43+
///
44+
/// Deletes obsolete channels, if present, from old versions of the app.
3945
//
4046
// NOTE when changing anything here: the changes will not take effect
4147
// for existing installs of the app! That's because we'll have already
@@ -52,11 +58,28 @@ class NotificationChannelManager {
5258
// settings for the channel -- like "override Do Not Disturb", or "use
5359
// a different sound", or "don't pop on screen" -- their changes get
5460
// reset. So this has to be done sparingly.
55-
//
56-
// If we do this, we should also look for any channel with the old
57-
// channel ID and delete it. See zulip-mobile's `createNotificationChannel`
58-
// in android/app/src/main/java/com/zulipmobile/notifications/NotificationChannelManager.kt .
59-
static Future<void> _ensureChannel() async {
61+
@visibleForTesting
62+
static Future<void> ensureChannel() async {
63+
// See if our current-version channel already exists; delete any obsolete
64+
// previous channels.
65+
var found = false;
66+
final channels = await _androidHost.getNotificationChannels();
67+
for (final channel in channels) {
68+
assert(channel != null); // TODO(#942)
69+
if (channel!.id == kChannelId) {
70+
found = true;
71+
} else {
72+
await _androidHost.deleteNotificationChannel(channel.id);
73+
}
74+
}
75+
76+
if (found) {
77+
// The channel already exists; nothing to do.
78+
return;
79+
}
80+
81+
// The channel doesn't exist. Create it.
82+
6083
await _androidHost.createNotificationChannel(NotificationChannel(
6184
id: kChannelId,
6285
name: 'Messages', // TODO(i18n)
@@ -81,7 +104,7 @@ class NotificationDisplayManager {
81104
if (launchDetails?.didNotificationLaunchApp ?? false) {
82105
_handleNotificationAppLaunch(launchDetails!.notificationResponse);
83106
}
84-
await NotificationChannelManager._ensureChannel();
107+
await NotificationChannelManager.ensureChannel();
85108
}
86109

87110
static void onFcmMessage(FcmMessage data, Map<String, dynamic> dataJson) {

test/model/binding.dart

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -554,20 +554,32 @@ class FakeAndroidNotificationHostApi implements AndroidNotificationHostApi {
554554
}
555555
List<NotificationChannel> _createdChannels = [];
556556

557+
/// Consumes the log of calls made to [getNotificationChannels],
558+
/// [deleteNotificationChannel] and [createNotificationChannel].
559+
///
560+
/// Returns a list of function names in the order they were invoked.
561+
List<String> takeChannelMethodCallLogs() {
562+
final result = _channelMethodCallLogs;
563+
_channelMethodCallLogs = [];
564+
return result;
565+
}
566+
List<String> _channelMethodCallLogs = [];
567+
557568
@override
558-
Future<List<NotificationChannel?>> getNotificationChannels() {
559-
// TODO: implement getNotificationChannels
560-
throw UnimplementedError();
569+
Future<List<NotificationChannel?>> getNotificationChannels() async {
570+
_channelMethodCallLogs.add('getNotificationChannels');
571+
return _createdChannels.toList(growable: false);
561572
}
562573

563574
@override
564-
Future<void> deleteNotificationChannel(String channelId) {
565-
// TODO: implement deleteNotificationChannel
566-
throw UnimplementedError();
575+
Future<void> deleteNotificationChannel(String channelId) async {
576+
_channelMethodCallLogs.add('deleteNotificationChannel');
577+
_createdChannels.removeWhere((e) => e.id == channelId);
567578
}
568579

569580
@override
570581
Future<void> createNotificationChannel(NotificationChannel channel) async {
582+
_channelMethodCallLogs.add('createNotificationChannel');
571583
_createdChannels.add(channel);
572584
}
573585

test/notifications/display_test.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,59 @@ void main() {
131131
NotificationChannelManager.kVibrationPattern)
132132
;
133133
});
134+
135+
test('channel is not recreated if one with same id already exists', () async {
136+
await NotificationChannelManager.ensureChannel();
137+
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs())
138+
.deepEquals(['getNotificationChannels', 'createNotificationChannel']);
139+
140+
await NotificationChannelManager.ensureChannel();
141+
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs())
142+
.deepEquals(['getNotificationChannels']);
143+
144+
check(testBinding.androidNotificationHost.takeCreatedChannels()).single
145+
..id.equals(NotificationChannelManager.kChannelId)
146+
..name.equals('Messages')
147+
..importance.equals(NotificationImportance.high)
148+
..lightsEnabled.equals(true)
149+
..vibrationPattern.isNotNull().deepEquals(
150+
NotificationChannelManager.kVibrationPattern);
151+
});
152+
153+
test('obsolete channels are removed', () async {
154+
await NotificationChannelManager.ensureChannel();
155+
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs())
156+
.deepEquals(['getNotificationChannels', 'createNotificationChannel']);
157+
158+
await testBinding.androidNotificationHost.createNotificationChannel(NotificationChannel(
159+
id: 'obsolete-1',
160+
name: 'Obsolete 1',
161+
importance: NotificationImportance.high,
162+
lightsEnabled: true,
163+
vibrationPattern: NotificationChannelManager.kVibrationPattern));
164+
await testBinding.androidNotificationHost.createNotificationChannel(NotificationChannel(
165+
id: 'obsolete-2',
166+
name: 'Obsolete 2',
167+
importance: NotificationImportance.high,
168+
lightsEnabled: true,
169+
vibrationPattern: NotificationChannelManager.kVibrationPattern));
170+
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs())
171+
.deepEquals(['createNotificationChannel', 'createNotificationChannel']);
172+
173+
await NotificationChannelManager.ensureChannel();
174+
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs())
175+
.deepEquals([
176+
'getNotificationChannels',
177+
'deleteNotificationChannel',
178+
'deleteNotificationChannel']);
179+
check(testBinding.androidNotificationHost.takeCreatedChannels()).single
180+
..id.equals(NotificationChannelManager.kChannelId)
181+
..name.equals('Messages')
182+
..importance.equals(NotificationImportance.high)
183+
..lightsEnabled.equals(true)
184+
..vibrationPattern.isNotNull().deepEquals(
185+
NotificationChannelManager.kVibrationPattern);
186+
});
134187
});
135188

136189
group('NotificationDisplayManager show', () {

0 commit comments

Comments
 (0)