diff --git a/README.md b/README.md index ce1168818..2166aad87 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,6 @@ Please make sure that you always read the tagged README for the version you're u Please report bugs or missing features. -## Lacking features - -### Android - -- Channels are not configurable. A default channel will be created with [IMPORTANCE_DEFAULT](https://developer.android.com/reference/android/app/NotificationManager#IMPORTANCE_DEFAULT), the id `default-channel-id` and the name `Default channel`. The name ist not translatable. - ## Basics The plugin creates the object `cordova.plugins.notification.local` and is accessible after *deviceready* has been fired. @@ -97,7 +91,7 @@ A notification does have a set of configurable properties. | attachments | | | | | autoClear | | | | | badge | | | | -| channel | x | - | Don't use it, it is not functional. Since Android 8: Channel-ID of the chanel. In the future it will be renamed to channelId | +| channel | | - | Channel-ID of the channel (see: createChannel). In the future it will be renamed to channelId | | clock | | | | | color | | | | | data | | | | @@ -118,14 +112,14 @@ A notification does have a set of configurable properties. | progressBar | x | - | Natively not supported by iOS, [see Stackoverflow](https://stackoverflow.com/questions/48500532/progress-view-in-local-notification/48500734#48500734) | | silent | | | | | smallIcon | | | | -| sound | (x) | (x) | Property available but not useable. Cannot used to play a sound file. In Android it's only possible up to Android 7.1. Since Android 8, the channels take precedence and a channel would have to be created with a sound file, but this is not implemented yet. In iOS it would be possible, but must be implemented too. | +| sound | (x) | (x) | Property available but not useable. In Android it may work up to Android 7.1. Since Android 8 it must be set with createChannel. In iOS it would be possible, but must be implemented too. | | sticky | | | | | summary | | | | | text | x | x | Text of the notification. For Android exists some special features: 1. It can be a JSONArray to [summarize](#summarizing) notifications. [NotificationCompat.MessagingStyle](https://developer.android.com/reference/androidx/core/app/NotificationCompat.MessagingStyle) will then be used. Using an JSONArray for iOS would result in a crash. 2. If the text contains line breaks (`\n`) the notification style [NotificationCompat.InboxStyle](https://developer.android.com/reference/androidx/core/app/NotificationCompat.InboxStyle) would be used. 3. If the text is longer then 44 chars, the notifications style [NotificationCompat.BigTextStyle](https://developer.android.com/reference/androidx/core/app/NotificationCompat.BigTextStyle) will be used. | | timeoutAfter | | | | | title | | | | | trigger | | | | -| vibrate | (x) | - | Android only. Currently not supported. Notification channels have to be integrated first. | +| vibrate | (x) | | Android only. Since Android 8 it must be set with channel. | | wakeup | | | | For their default values see: @@ -523,11 +517,60 @@ See the sample app for how to use them. | :------- | :---------------- | :-------------- | :------------- | :------------ | :--------------- | | schedule | cancelAll | isTriggered | get | removeActions | un | | update | hasPermission | getType | getAll | hasActions | fireQueuedEvents | -| clear | requestPermission | getIds | getScheduled | getDefaults | -| clearAll | isPresent | getScheduledIds | getTriggered | setDefaults | +| clear | requestPermission | getIds | getScheduled | getDefaults | createChannel | +| clearAll | isPresent | getScheduledIds | getTriggered | setDefaults | canScheduleExactAlarms| | cancel | isScheduled | getTriggeredIds | addActions | on | + +## Android Notification Channels + +On Android 8 and above a Notification Channel is required and is the only way to set a non-default sound, vibration or importance. + +A default channel is automatically created with [IMPORTANCE_DEFAULT](https://developer.android.com/reference/android/app/NotificationManager#IMPORTANCE_DEFAULT), the id `default-channel-id` and the name `Default channel`. (The name is not translatable.) Any notification that doesn't specify a `channel` will use this default channel with default sound and vibration settings. + +To customize vibration, sound, or importance, create a channel as follows: + +```js +cordova.plugins.notification.local.createChannel({ + channelId:'my_ch_id', // string + channelName:'My Channel Name', // string + description:"Description of channel", // string (optional) + sound: 'file://audio/ring.mp3', // string (optional) + vibrate: true, // bool (optional) + importance: 3, // int (optional) 0 to 4 + soundUsage: 5, // int (optional) + }, success_callback) +``` + +**Options for `importance`** [Documentation](https://developer.android.com/reference/android/app/NotificationManager#IMPORTANCE_DEFAULT) +0 IMPORTANCE_NONE +1 IMPORTANCE_MIN +2 IMPORTANCE_LOW +3 IMPORTANCE_DEFAULT +4 IMPORTANCE_HIGH + +**Options for `soundUsage`** [Documentation](https://developer.android.com/reference/android/media/AudioAttributes.Builder#setUsage(int)) +0: USAGE_UNKNOWN +1: USAGE_MEDIA +2: USAGE_VOICE_COMMUNICATION +3: USAGE_VOICE_COMMUNICATION_SIGNALLING +4: USAGE_ALARM +5: USAGE_NOTIFICATION +6: USAGE_NOTIFICATION_RINGTONE +7: USAGE_NOTIFICATION_COMMUNICATION_REQUEST +8: USAGE_NOTIFICATION_COMMUNICATION_INSTANT +9: USAGE_NOTIFICATION_COMMUNICATION_DELAYED +10: USAGE_NOTIFICATION_EVENT +11: USAGE_ASSISTANCE_ACCESSIBILITY +12: USAGE_ASSISTANCE_NAVIGATION_GUIDANCE +13: USAGE_ASSISTANCE_SONIFICATION +14: USAGE_GAME +16: USAGE_ASSISTANT + +Then use the `channelId` value as the `channel` when scheduling a notification. It is not possible to programmatically update a channel once it has been created. + + ## Installation The plugin can be installed via [Cordova-CLI][CLI] and is publicly available on [NPM][npm]. diff --git a/src/android/LocalNotification.java b/src/android/LocalNotification.java index a49dea629..ac102d504 100644 --- a/src/android/LocalNotification.java +++ b/src/android/LocalNotification.java @@ -156,6 +156,9 @@ public boolean execute (final String action, final JSONArray args, public void run() { if (action.equals("ready")) { deviceready(); + } else + if (action.equals("createChannel")) { + createChannel(args, command); } else if (action.equals("check")) { check(command); @@ -334,6 +337,21 @@ private void schedule (JSONArray toasts, CallbackContext command) { } check(command); + } + + /** + * Create Notification channel with options. + * + * @param args The channel options. + * @param command The callback context used when calling back into + * JavaScript. + */ + private void createChannel (JSONArray args, CallbackContext command) { + Manager mgr = getNotMgr(); + JSONObject options = args.optJSONObject(0); + mgr.createChannel(options); + + command.success(); } /** diff --git a/src/android/notification/Manager.java b/src/android/notification/Manager.java index ebe350ed0..9375dcfd2 100644 --- a/src/android/notification/Manager.java +++ b/src/android/notification/Manager.java @@ -31,6 +31,9 @@ import android.service.notification.StatusBarNotification; import androidx.core.app.NotificationManagerCompat; import android.app.AlarmManager; +import android.media.AudioAttributes; +import android.net.Uri; + import org.json.JSONException; import org.json.JSONObject; @@ -48,6 +51,7 @@ import static androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT; import static de.appplant.cordova.plugin.notification.Notification.PREF_KEY_ID; import static de.appplant.cordova.plugin.notification.Notification.Type.TRIGGERED; +import de.appplant.cordova.plugin.notification.util.AssetUtil; /** * Central way to access all or single local notifications set by specific @@ -140,6 +144,52 @@ private void createDefaultChannel() { mgr.createNotificationChannel(channel); } + /** + * Create Notification channel with options + * @param options Set of channel options. + * + */ + public void createChannel(JSONObject options) { + NotificationManager mgr = getNotMgr(); + + if (SDK_INT < O) + return; + + String channelId = options.optString("channelId", ""); + CharSequence channelName = options.optString("channelName", ""); + int importance = options.optInt("importance",IMPORTANCE_DEFAULT); + NotificationChannel channel = mgr.getNotificationChannel(channelId); + + if (channel != null) + return; + + channel = new NotificationChannel(channelId, channelName, importance); + + if(options.has("vibrate")){ + Boolean shouldVibrate = options.optBoolean("vibrate", false); + channel.enableVibration(shouldVibrate); + } + + if(options.has("sound")){ + AssetUtil assets = AssetUtil.getInstance(this.context); + Uri soundUri = assets.parse(options.optString("sound", null)); + + if (!soundUri.equals(Uri.EMPTY)) { + AudioAttributes attributes = new AudioAttributes.Builder().setUsage(options.optInt("soundUsage",AudioAttributes.USAGE_NOTIFICATION)).build(); + channel.setSound(soundUri, attributes); + }else{ + channel.setSound(null, null); + } + } + + if(options.has("description")){ + channel.setDescription(options.optString("description", " ")); + } + + mgr.createNotificationChannel(channel); + } + + /** * Update local notification specified by ID. * diff --git a/www/local-notification.js b/www/local-notification.js index 4f2cf87a0..2b77e88fa 100644 --- a/www/local-notification.js +++ b/www/local-notification.js @@ -63,6 +63,32 @@ exports._defaults = { // Event listener exports._listener = {}; +/** + * Create Notification Channel. Android only. + * + * @param [ Object ] options channel config options. + * @param [ Function ] callback The function to be exec as the callback. + * @param [ Object ] scope The callback function's scope. + * + * @return [ Void ] + */ +exports.createChannel = function (options, callback, scope) { + + if(typeof(options.channelId) != "string"){ + console.error('createChannel error: channelId must be a string.'); + return false; + } + + if(typeof(options.channelName) != "string"){ + console.error('createChannel error: channelName must be a string.'); + return false; + } + + var toasts = this._toArray(options); + + this._exec('createChannel', toasts, callback, scope); +}; + /** * Check permission to show notifications. *