diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java index 287c32011a..b015711dff 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java @@ -12,6 +12,7 @@ import com.getcapacitor.plugin.notification.LocalNotification; import com.getcapacitor.plugin.notification.LocalNotificationManager; import com.getcapacitor.plugin.notification.NotificationAction; +import com.getcapacitor.plugin.notification.NotificationChannelManager; import com.getcapacitor.plugin.notification.NotificationStorage; import org.json.JSONArray; @@ -29,6 +30,7 @@ public class LocalNotifications extends Plugin { private LocalNotificationManager manager; private NotificationStorage notificationStorage; + private NotificationChannelManager notificationChannelManager; public LocalNotifications() { } @@ -39,6 +41,7 @@ public void load() { notificationStorage = new NotificationStorage(getContext()); manager = new LocalNotificationManager(notificationStorage, getActivity()); manager.createNotificationChannel(); + notificationChannelManager = new NotificationChannelManager(getActivity()); } @Override @@ -118,5 +121,20 @@ public void areEnabled(PluginCall call) { call.success(data); } + @PluginMethod() + public void createChannel(PluginCall call) { + notificationChannelManager.createChannel(call); + } + + @PluginMethod() + public void deleteChannel(PluginCall call) { + notificationChannelManager.deleteChannel(call); + } + + @PluginMethod() + public void listChannels(PluginCall call) { + notificationChannelManager.listChannels(call); + } + } diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/PushNotifications.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/PushNotifications.java index 084c3982c4..55f95c199b 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/PushNotifications.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/PushNotifications.java @@ -1,21 +1,14 @@ package com.getcapacitor.plugin; import android.app.Notification; -import android.app.NotificationChannel; import android.app.NotificationManager; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.graphics.Color; -import android.media.AudioAttributes; import android.os.Build; import android.os.Bundle; import android.service.notification.StatusBarNotification; -import androidx.core.app.NotificationCompat; import android.net.Uri; -import android.util.Log; - import com.getcapacitor.Bridge; import com.getcapacitor.JSArray; import com.getcapacitor.JSObject; @@ -24,6 +17,7 @@ import com.getcapacitor.PluginCall; import com.getcapacitor.PluginHandle; import com.getcapacitor.PluginMethod; +import com.getcapacitor.plugin.notification.NotificationChannelManager; import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; import com.google.firebase.iid.FirebaseInstanceId; @@ -35,26 +29,15 @@ import org.json.JSONObject; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; @NativePlugin() public class PushNotifications extends Plugin { - public static String CHANNEL_ID = "id"; - public static String CHANNEL_NAME = "name"; - public static String CHANNEL_DESCRIPTION = "description"; - public static String CHANNEL_IMPORTANCE = "importance"; - public static String CHANNEL_VISIBILITY = "visibility"; - public static String CHANNEL_SOUND = "sound"; - public static String CHANNEL_USE_LIGHTS = "lights"; - public static String CHANNEL_LIGHT_COLOR = "lightColor"; - public static Bridge staticBridge = null; public static RemoteMessage lastMessage = null; public NotificationManager notificationManager; - + private NotificationChannelManager notificationChannelManager; private static final String EVENT_TOKEN_CHANGE = "registration"; private static final String EVENT_TOKEN_ERROR = "registrationError"; @@ -67,6 +50,7 @@ public void load() { fireNotification(lastMessage); lastMessage = null; } + notificationChannelManager = new NotificationChannelManager(getActivity(), notificationManager); } @Override @@ -187,85 +171,17 @@ public void removeAllDeliveredNotifications(PluginCall call) { @PluginMethod() public void createChannel(PluginCall call) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - JSObject channel = new JSObject(); - channel.put(CHANNEL_ID, call.getString(CHANNEL_ID)); - channel.put(CHANNEL_NAME, call.getString(CHANNEL_NAME)); - channel.put(CHANNEL_DESCRIPTION, call.getString(CHANNEL_DESCRIPTION, "")); - channel.put(CHANNEL_VISIBILITY, call.getInt(CHANNEL_VISIBILITY, NotificationCompat.VISIBILITY_PUBLIC)); - channel.put(CHANNEL_IMPORTANCE, call.getInt(CHANNEL_IMPORTANCE)); - channel.put(CHANNEL_SOUND, call.getString(CHANNEL_SOUND, null)); - channel.put(CHANNEL_USE_LIGHTS, call.getBoolean(CHANNEL_USE_LIGHTS, false)); - channel.put(CHANNEL_LIGHT_COLOR, call.getString(CHANNEL_LIGHT_COLOR, null)); - createChannel(channel); - call.success(); - } else { - call.unavailable(); - } + notificationChannelManager.createChannel(call); } @PluginMethod() public void deleteChannel(PluginCall call) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - String channelId = call.getString("id"); - notificationManager.deleteNotificationChannel(channelId); - call.success(); - } else { - call.unavailable(); - } + notificationChannelManager.deleteChannel(call); } @PluginMethod() public void listChannels(PluginCall call) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - List notificationChannels = notificationManager.getNotificationChannels(); - JSArray channels = new JSArray(); - for (NotificationChannel notificationChannel : notificationChannels) { - JSObject channel = new JSObject(); - channel.put(CHANNEL_ID, notificationChannel.getId()); - channel.put(CHANNEL_NAME, notificationChannel.getName()); - channel.put(CHANNEL_DESCRIPTION, notificationChannel.getDescription()); - channel.put(CHANNEL_IMPORTANCE, notificationChannel.getImportance()); - channel.put(CHANNEL_VISIBILITY, notificationChannel.getLockscreenVisibility()); - channel.put(CHANNEL_SOUND, notificationChannel.getSound()); - channel.put(CHANNEL_USE_LIGHTS, notificationChannel.shouldShowLights()); - channel.put(CHANNEL_LIGHT_COLOR, String.format("#%06X", (0xFFFFFF & notificationChannel.getLightColor()))); - Log.d(getLogTag(), "visibility " + notificationChannel.getLockscreenVisibility()); - Log.d(getLogTag(), "importance " + notificationChannel.getImportance()); - channels.put(channel); - } - JSObject result = new JSObject(); - result.put("channels", channels); - call.success(result); - } else { - call.unavailable(); - } - } - - private void createChannel(JSObject channel) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - NotificationChannel notificationChannelChannel = new NotificationChannel(channel.getString(CHANNEL_ID), channel.getString(CHANNEL_NAME), channel.getInteger(CHANNEL_IMPORTANCE)); - notificationChannelChannel.setDescription(channel.getString(CHANNEL_DESCRIPTION)); - notificationChannelChannel.setLockscreenVisibility(channel.getInteger(CHANNEL_VISIBILITY)); - notificationChannelChannel.enableLights(channel.getBool(CHANNEL_USE_LIGHTS)); - String lightColor = channel.getString(CHANNEL_LIGHT_COLOR); - if (lightColor != null) { - try { - notificationChannelChannel.setLightColor(Color.parseColor(lightColor)); - } catch (IllegalArgumentException ex) { - Log.e(getLogTag(), "Invalid color provided for light color."); - } - } - String sound = channel.getString(CHANNEL_SOUND, null); - if (sound != null && !sound.isEmpty()) { - AudioAttributes audioAttributes = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ALARM).build(); - Uri soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getContext().getPackageName() + "/raw/" + sound); - notificationChannelChannel.setSound(soundUri, audioAttributes); - } - notificationManager.createNotificationChannel(notificationChannelChannel); - } + notificationChannelManager.listChannels(call); } public void sendToken(String token) { diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java index 74ff5ff6dd..5d87e04a7f 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java @@ -41,6 +41,7 @@ public class LocalNotification { private JSObject extra; private List attachments; private LocalNotificationSchedule schedule; + private String channelId; private String source; @@ -157,6 +158,14 @@ public void setGroupSummary(boolean groupSummary) { this.groupSummary = groupSummary; } + public String getChannelId() { + return channelId; + } + + public void setChannelId(String channelId) { + this.channelId = channelId; + } + /** * Build list of the notifications from remote plugin call */ @@ -195,6 +204,7 @@ public static List buildNotificationList(PluginCall call) { activeLocalNotification.setIconColor(notification.getString("iconColor")); activeLocalNotification.setAttachments(LocalNotificationAttachment.getAttachments(notification)); activeLocalNotification.setGroupSummary(notification.getBoolean("groupSummary", false)); + activeLocalNotification.setChannelId(notification.getString("channelId")); try { activeLocalNotification.setSchedule(new LocalNotificationSchedule(notification)); } catch (ParseException e) { diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java index 0bc3935176..b903377374 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java @@ -99,7 +99,6 @@ public JSObject handleNotificationActionPerformed(Intent data, NotificationStora * Create notification channel */ public void createNotificationChannel() { - // TODO allow to create multiple channels // Create the NotificationChannel, but only on API 26+ because // the NotificationChannel class is new and not in the support library if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -155,7 +154,11 @@ public JSONArray schedule(PluginCall call, List localNotifica // TODO media style notification support NotificationCompat.MediaStyle // TODO custom small/large icons private void buildNotification(NotificationManagerCompat notificationManager, LocalNotification localNotification, PluginCall call) { - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this.context, DEFAULT_NOTIFICATION_CHANNEL_ID) + String channelId = DEFAULT_NOTIFICATION_CHANNEL_ID; + if (localNotification.getChannelId() != null) { + channelId = localNotification.getChannelId(); + } + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this.context, channelId) .setContentTitle(localNotification.getTitle()) .setContentText(localNotification.getBody()) .setAutoCancel(true) diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationChannelManager.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationChannelManager.java new file mode 100644 index 0000000000..6a76adf8da --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationChannelManager.java @@ -0,0 +1,129 @@ +package com.getcapacitor.plugin.notification; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.Color; +import android.media.AudioAttributes; +import android.net.Uri; +import android.util.Log; + + +import androidx.core.app.NotificationCompat; + +import com.getcapacitor.JSArray; +import com.getcapacitor.JSObject; +import com.getcapacitor.PluginCall; + +import java.util.List; + +public class NotificationChannelManager { + + private Context context; + private NotificationManager notificationManager; + + public NotificationChannelManager(Context context) { + this.context = context; + this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + } + + public NotificationChannelManager(Context context, NotificationManager manager) { + this.context = context; + this.notificationManager = manager; + } + + private static final String TAG = "NotificationChannel: "; + + + private static String CHANNEL_ID = "id"; + private static String CHANNEL_NAME = "name"; + private static String CHANNEL_DESCRIPTION = "description"; + private static String CHANNEL_IMPORTANCE = "importance"; + private static String CHANNEL_VISIBILITY = "visibility"; + private static String CHANNEL_SOUND = "sound"; + private static String CHANNEL_USE_LIGHTS = "lights"; + private static String CHANNEL_LIGHT_COLOR = "lightColor"; + + public void createChannel(PluginCall call) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + JSObject channel = new JSObject(); + channel.put(CHANNEL_ID, call.getString(CHANNEL_ID)); + channel.put(CHANNEL_NAME, call.getString(CHANNEL_NAME)); + channel.put(CHANNEL_DESCRIPTION, call.getString(CHANNEL_DESCRIPTION, "")); + channel.put(CHANNEL_VISIBILITY, call.getInt(CHANNEL_VISIBILITY, NotificationCompat.VISIBILITY_PUBLIC)); + channel.put(CHANNEL_IMPORTANCE, call.getInt(CHANNEL_IMPORTANCE)); + channel.put(CHANNEL_SOUND, call.getString(CHANNEL_SOUND, null)); + channel.put(CHANNEL_USE_LIGHTS, call.getBoolean(CHANNEL_USE_LIGHTS, false)); + channel.put(CHANNEL_LIGHT_COLOR, call.getString(CHANNEL_LIGHT_COLOR, null)); + createChannel(channel); + call.success(); + } else { + call.unavailable(); + } + } + public void createChannel(JSObject channel) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + NotificationChannel notificationChannel = new NotificationChannel(channel.getString(CHANNEL_ID), channel.getString(CHANNEL_NAME), channel.getInteger(CHANNEL_IMPORTANCE)); + notificationChannel.setDescription(channel.getString(CHANNEL_DESCRIPTION)); + notificationChannel.setLockscreenVisibility(channel.getInteger(CHANNEL_VISIBILITY)); + notificationChannel.enableLights(channel.getBool(CHANNEL_USE_LIGHTS)); + String lightColor = channel.getString(CHANNEL_LIGHT_COLOR); + if (lightColor != null) { + try { + notificationChannel.setLightColor(Color.parseColor(lightColor)); + } catch (IllegalArgumentException ex) { + Log.e(TAG, "Invalid color provided for light color."); + } + } + String sound = channel.getString(CHANNEL_SOUND, null); + if (sound != null && !sound.isEmpty()) { + if (sound.contains(".")) { + sound = sound.substring(0, sound.lastIndexOf('.')); + } + AudioAttributes audioAttributes = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ALARM).build(); + Uri soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/raw/" + sound); + notificationChannel.setSound(soundUri, audioAttributes); + } + notificationManager.createNotificationChannel(notificationChannel); + } + } + + public void deleteChannel(PluginCall call) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + String channelId = call.getString("id"); + notificationManager.deleteNotificationChannel(channelId); + call.success(); + } else { + call.unavailable(); + } + } + + public void listChannels(PluginCall call) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + List notificationChannels = notificationManager.getNotificationChannels(); + JSArray channels = new JSArray(); + for (NotificationChannel notificationChannel : notificationChannels) { + JSObject channel = new JSObject(); + channel.put(CHANNEL_ID, notificationChannel.getId()); + channel.put(CHANNEL_NAME, notificationChannel.getName()); + channel.put(CHANNEL_DESCRIPTION, notificationChannel.getDescription()); + channel.put(CHANNEL_IMPORTANCE, notificationChannel.getImportance()); + channel.put(CHANNEL_VISIBILITY, notificationChannel.getLockscreenVisibility()); + channel.put(CHANNEL_SOUND, notificationChannel.getSound()); + channel.put(CHANNEL_USE_LIGHTS, notificationChannel.shouldShowLights()); + channel.put(CHANNEL_LIGHT_COLOR, String.format("#%06X", (0xFFFFFF & notificationChannel.getLightColor()))); + Log.d(TAG, "visibility " + notificationChannel.getLockscreenVisibility()); + Log.d(TAG, "importance " + notificationChannel.getImportance()); + channels.put(channel); + } + JSObject result = new JSObject(); + result.put("channels", channels); + call.success(result); + } else { + call.unavailable(); + } + } +} diff --git a/core/src/core-plugin-definitions.ts b/core/src/core-plugin-definitions.ts index 5ab178708f..b3fd0b3115 100644 --- a/core/src/core-plugin-definitions.ts +++ b/core/src/core-plugin-definitions.ts @@ -1109,6 +1109,12 @@ export interface LocalNotification { * (should be used with the `group` property). */ groupSummary?: boolean; + /** + * Android only: set the notification channel on which local notification + * will generate. If channel with the given name does not exist then the + * notification will not fire. If not provided, it will use the default channel. + */ + channelId?: string; } export interface LocalNotificationSchedule { @@ -1148,6 +1154,9 @@ export interface LocalNotificationsPlugin extends Plugin { registerActionTypes(options: { types: LocalNotificationActionType[] }): Promise; cancel(pending: LocalNotificationPendingList): Promise; areEnabled(): Promise; + createChannel(channel: NotificationChannel): Promise; + deleteChannel(channel: NotificationChannel): Promise; + listChannels(): Promise; requestPermission(): Promise; addListener(eventName: 'localNotificationReceived', listenerFunc: (notification: LocalNotification) => void): PluginListenerHandle; addListener(eventName: 'localNotificationActionPerformed', listenerFunc: (notificationAction: LocalNotificationActionPerformed) => void): PluginListenerHandle; @@ -1553,7 +1562,7 @@ export interface PushNotificationDeliveredList { notifications: PushNotification[]; } -export interface PushNotificationChannel { +export interface NotificationChannel { id: string; name: string; description?: string; @@ -1564,8 +1573,8 @@ export interface PushNotificationChannel { lightColor?: string; } -export interface PushNotificationChannelList { - channels: PushNotificationChannel[]; +export interface NotificationChannelList { + channels: NotificationChannel[]; } export interface PushNotificationsPlugin extends Plugin { @@ -1574,9 +1583,9 @@ export interface PushNotificationsPlugin extends Plugin { getDeliveredNotifications(): Promise; removeDeliveredNotifications(delivered: PushNotificationDeliveredList): Promise; removeAllDeliveredNotifications(): Promise; - createChannel(channel: PushNotificationChannel): Promise; - deleteChannel(channel: PushNotificationChannel): Promise; - listChannels(): Promise; + createChannel(channel: NotificationChannel): Promise; + deleteChannel(channel: NotificationChannel): Promise; + listChannels(): Promise; addListener(eventName: 'registration', listenerFunc: (token: PushNotificationToken) => void): PluginListenerHandle; addListener(eventName: 'registrationError', listenerFunc: (error: any) => void): PluginListenerHandle; addListener(eventName: 'pushNotificationReceived', listenerFunc: (notification: PushNotification) => void): PluginListenerHandle; diff --git a/core/src/web/local-notifications.ts b/core/src/web/local-notifications.ts index d22383b155..3ce86b4fa7 100644 --- a/core/src/web/local-notifications.ts +++ b/core/src/web/local-notifications.ts @@ -7,7 +7,9 @@ import { LocalNotificationActionType, LocalNotification, LocalNotificationScheduleResult, - NotificationPermissionResponse + NotificationPermissionResponse, + NotificationChannel, + NotificationChannelList } from '../core-plugin-definitions'; import { PermissionsRequestResult } from '../definitions'; @@ -21,6 +23,18 @@ export class LocalNotificationsPluginWeb extends WebPlugin implements LocalNotif platforms: ['web'] }); } + + createChannel(channel: NotificationChannel): Promise { + throw new Error('Feature not available in the browser. ' + channel.id); + } + + deleteChannel(channel: NotificationChannel): Promise { + throw new Error('Feature not available in the browser. ' + channel.id); + } + + listChannels(): Promise { + throw new Error('Feature not available in the browser'); + } sendPending() { const toRemove: LocalNotification[] = []; @@ -103,7 +117,7 @@ export class LocalNotificationsPluginWeb extends WebPlugin implements LocalNotif if (result === 'denied' || result === 'default') { granted = false; } - resolve({granted}); + resolve({ granted }); }); }); } @@ -116,7 +130,7 @@ export class LocalNotificationsPluginWeb extends WebPlugin implements LocalNotif return; } resolve({ - results: [ result ] + results: [result] }); }); }); diff --git a/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m b/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m index 64be5e1961..b6d36d6f8e 100644 --- a/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m +++ b/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m @@ -94,6 +94,9 @@ CAP_PLUGIN_METHOD(getPending, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(registerActionTypes, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(areEnabled, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(createChannel, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(deleteChannel, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(listChannels, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(removeAllListeners, CAPPluginReturnNone); ) diff --git a/ios/Capacitor/Capacitor/Plugins/LocalNotifications.swift b/ios/Capacitor/Capacitor/Plugins/LocalNotifications.swift index 0b52f39175..c0032a71c8 100644 --- a/ios/Capacitor/Capacitor/Plugins/LocalNotifications.swift +++ b/ios/Capacitor/Capacitor/Plugins/LocalNotifications.swift @@ -511,5 +511,17 @@ public class CAPLocalNotificationsPlugin : CAPPlugin { } return opts } + + @objc func createChannel(_ call: CAPPluginCall) { + call.unimplemented() + } + + @objc func deleteChannel(_ call: CAPPluginCall) { + call.unimplemented() + } + + @objc func listChannels(_ call: CAPPluginCall) { + call.unimplemented() + } }