/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #pragma once namespace juce { /** Singleton class responsible for push notifications functionality. Both remote and local notifications are supported. To get information about notifications, register a listener on your application startup. It is best to register the listener as soon as possible, because your application can be launched from a push notification too. To send a local notification create an instance of Notification, fill the necessary fields and call PushNotifications::sendLocalNotification(). When receiving local or remote notifications, inspect the Notification's fields for notification details. Bear in mind that some fields will not be available when receiving a remote notification. @tags{GUI} */ class JUCE_API PushNotifications : private DeletedAtShutdown { public: #ifndef DOXYGEN JUCE_DECLARE_SINGLETON (PushNotifications, false) #endif //============================================================================== /** Represents a notification that can be sent or received. */ struct Notification { Notification() = default; Notification (const Notification& other); /** Checks whether a given notification is correctly configured for a given OS. */ bool isValid() const noexcept; /** Represents an action on a notification that can be presented as a button or a text input. On Android, each notification has its action specified explicitly, on iOS you configure an allowed set of actions on startup and pack them into categories (see Settings). */ struct Action { /** Controls the appearance of this action. */ enum Style { button, /**< Show this action as a button. */ text /**< Show this action as a text input field (on Android API 20 or higher is required). */ }; /** @name Common fields */ /**@{*/ Style style = button; String title; /**< Required. the name of the action displayed to the user. */ String textInputPlaceholder; /**< Optional: placeholder text for text input notification. Note that it will be ignored if button style is used. */ var parameters; /**< Optional: additional parameters that can be passed. */ /**@}*/ /** @name iOS only fields */ /**@{*/ String identifier; /**< Required: unique identifier. This should be one of the identifiers set with requestPermissionsWithSettings(). */ bool triggerInBackground = false; /**< Whether the app can process the action in background. */ bool destructive = false; /**< Whether to display the action as destructive. */ String textInputButtonText; /**< Optional: Text displayed on text input notification button (from iOS 10 only). Note that it will be ignored if style is set to Style::button. */ /**@}*/ /** @name Android only fields */ /**@{*/ String icon; /**< Optional: name of an icon file (without an extension) to be used for this action. This must be the name of one of the image files included into resources when exporting an Android project (see "Extra Android Raw Resources" setting in Projucer). Note that not all Android versions support an icon for an action, though it is recommended to provide it nevertheless. */ StringArray allowedResponses; /**< Optional: a list of possible answers if the answer set is limited. When left empty, then the user will be able to input any text. */ /**@}*/ }; //============================================================================== /** @name Common fields */ /**@{*/ String identifier; /**< Required: unique id that can be used to later dismiss the notification (on iOS available from version 10). */ String title; /**< Required: the title of the notification, usually displayed in the first row. */ String body; /**< Required: the content of the notification, usually displayed in the second row. */ String subtitle; /**< Optional: additional text, that may be displayed e.g. in the third row or in the header area. Note that on Android, depending on OS version, this may fight for space with other components of the notification, so use this field judiciously. On iOS available from version 10. On Android available from API 16. */ String groupId; /**< Optional: allows the OS to visually group, collapse, and expand a set of notifications, note that OS may automatically group notifications if no groupId is specified. Available on Android API 20 or above and iOS 10 or above. */ int badgeNumber = 0; /**< Optional: on platforms that support it, can set a number this notification represents. */ URL soundToPlay; /**< Optional: empty when the notification should be silent. When the name is set to "default_os_sound", then a default sound will be used. For a custom sound on OSX, set the URL to the name of a sound file (preferably without an extension) and place the sound file directly in bundle's "Resources" directory (you can use "Xcode Resource" tickbox in Projucer to achieve that), i.e. it cannot be in a subdirectory of "Resources" like "Resources/sound". Alternatively, if a sound file cannot be found in bundle's "Resources" directory, the OS may look for the sound in the following paths: "~/Library/Sounds", "/Library/Sounds", "/Network/Library/Sounds", "/System/Library/Sounds". For a custom sound on iOS, set the URL to a relative path within your bundle, including file extension. For instance, if your bundle contains "sounds" folder with "my_sound.caf" file, then the URL should be "sounds/my_sound.caf". For a custom sound on Android, set URL to the name of a raw resource file (without an extension) that was included when exporting an Android project in Projucer (see "Extra Android Raw Resources" setting). */ var properties; /**< Optional: collection of additional properties that may be passed as a dictionary. */ /**@}*/ //============================================================================== /** @name iOS only fields */ /**@{*/ String category; /**< Required: determines set of actions that will appear (as per setup done in requestPermissionsWithSettings()). */ double triggerIntervalSec = 0.; /**< Optional: specifies number of seconds before the notification should trigger. */ bool repeat = false; /**< Optional: allows the notification to continuously retrigger after triggerIntervalSec seconds. Available from iOS 10. */ /**@}*/ //============================================================================== /** @name Android only fields */ /**@{*/ String icon; /**< Required: name of an icon file (without an extension) to be used for this notification. This must be the name of one of the image files included into resources when exporting an Android project (see "Extra Android Raw Resources" setting in Projucer). */ String channelId; /**< Required for Android API level 26 or above: specifies notification channel id. Refer to setupChannels(). Ignored on earlier Android versions. */ Image largeIcon; /**< Optional: an additional large icon displayed in the notification content view. */ String tickerText; /**< Optional: ticker text used for accessibility services. */ Array actions; /**< Optional: actions associated with the notification. Note that the OS may allow only a limited number of actions to be presented, so always present most important actions first. Available from Android API 16 or above. */ /** Used to represent a progress of some operation. */ struct Progress { int max = 0; /**< Max possible value of a progress. A typical usecase is to set max to 100 and increment current's value as percentage complete. */ int current = 0; /**< Current progress value, should be from 0 to max. */ bool indeterminate = false; /**< If true, then the progress represents a continuing activity indicator with ongoing animation and no numeric value. */ }; Progress progress; /**< Optional: set to default (0, 0, false), to disable progress display. */ /** Metadata that can be used by the OS to better handle the notification, depending on its priority. */ enum Type { unspecified, /**< Category not set. */ alarm, /**< Alarm or timer. */ call, /**< Incoming voice/video call or similar. */ email, /**< Async message like email. */ error, /**< Error in background operation or authentication status. */ event, /**< Calendar event. */ message, /**< Incoming message (sms, instant message etc.). */ taskProgress, /**< Progress for a long-running background operation. */ promo, /**< Promotion or advertisement. */ recommendation, /**< Specific, single thing related recommendation. */ reminder, /**< User-scheduled reminder. */ service, /**< Running background service. */ social, /**< Social network or sharing update. */ status, /**< Ongoing information about device or contextual status. */ system, /**< System or device status update. */ transport /**< Media transport control for playback. */ }; /** Metadata used as a hint to the OS about the priority of the notification. */ enum Priority { veryLow = -2, low = -1, medium = 0, high = 1, veryHigh = 2 }; String person; /**< Optional: additional metadata used as a hint to OS that a notification is related to a specific person. Can be useful for instance messaging apps. Available from Android API 21 or above. */ Type type = unspecified; /**< Optional. Available from Android API 21 or above. */ Priority priority = medium; /**< Optional. Available from Android API 16 or above. */ /** Describes how to show the notification when the screen is locked. Available from Android API 21 or above. */ enum LockScreenAppearance { dontShow = -1, /**< The notification is not allowed on the lock screen */ showPartially = 0, /**< Only some information is allowed on the lock screen */ showCompletely = 1 /**< The entire notification is allowed on the lock screen */ }; LockScreenAppearance lockScreenAppearance = showPartially; /**< Optional. */ std::unique_ptr publicVersion; /**< Optional: if you set lockScreenAppearance to showPartially, then you can provide "public version" of your notification that will be displayed on the lock screen. This way you can control what information is visible when the screen is locked. */ String groupSortKey; /**< Optional: Used to order notifications within the same group. Available from Android API 20 or above. */ bool groupSummary = false; /**< Optional: if true, then this notification will be a group summary of the group set with groupId. Available from Android API 20 or above. */ Colour accentColour; /**< Optional: sets accent colour. The default colour will be used if accentColour is not set. Available from Android API 21 or above. */ Colour ledColour; /**< Optional: Sets the led colour. The hardware will do its best to approximate the colour. The default colour will be used if ledColour is not set. */ /** Allows to control the time the device's led is on and off. */ struct LedBlinkPattern { int msToBeOn = 0; /**< The led will be on for the given number of milliseconds, after which it will turn off. */ int msToBeOff = 0; /**< The led will be off for the given number of milliseconds, after which it will turn on. */ }; LedBlinkPattern ledBlinkPattern; /**< Optional. */ Array vibrationPattern; /**< Optional: sets the vibration pattern in milliseconds. The first value indicates how long to wait until vibration starts. The second value indicates how long to vibrate. The third value will say how long to not vibrate and so on. For instance, if the pattern is: 1000, 2000, 3000, 4000 - then one second after receiving a notification the device will vibrate for two seconds, followed by 3 seconds of no vibration and finally, 4 seconds of vibration. */ bool shouldAutoCancel = true; /**< Optional: If true, the notification will be automatically cancelled when a user clicks it in the panel. */ bool localOnly = true; /**< Optional: whether or not the notification should bridge to other devices. Available from Android API 20 or above. */ bool ongoing = false; /**< Optional: If true, then it cannot be dismissed by the user and it must be dismissed manually. Typically used for ongoing background tasks that the user is actively engaged with. To dismiss such notification, you need to call removeDeliveredNotification() or removeAllDeliveredNotifications(). */ bool alertOnlyOnce = false; /**< Optional: Set this flag if you would only like the sound, vibrate and ticker to be played if the notification is not already showing. */ /** Controls timestamp visibility and format. */ enum TimestampVisibility { off, /**< Do not show timestamp. */ normal, /**< Show normal timestamp. */ chronometer, /**< Show chronometer as a stopwatch. Available from Android API 16 or above. */ countDownChronometer /**< Set the chronometer to count down instead of counting up. Available from Android API 24 or above.*/ }; TimestampVisibility timestampVisibility = normal; /**< Optional. */ /** Controls badge icon type to use if a notification is shown as a badge. Available from Android API 26 or above. */ enum BadgeIconType { none, small, large }; BadgeIconType badgeIconType = large; /** Controls sound and vibration behaviour for group notifications. Available from Android API 26 or above. */ enum GroupAlertBehaviour { alertAll, /**< both child notifications and group notifications should produce sound and vibration. */ AlertSummary, /**< all child notifications in the group should have no sound nor vibration, even if corresponding notification channel has sounds and vibrations enabled. */ AlertChildren /**< summary notifications in the group should have no sound nor vibration, even if corresponding notification channel has sounds and vibrations enabled. */ }; GroupAlertBehaviour groupAlertBehaviour = alertAll; int timeoutAfterMs = 0; /**< specifies a duration in milliseconds, after which the notification should be cancelled, if it is not already canceled. Available from Android API 26 or above. */ /**@}*/ }; //============================================================================== /** Describes settings we want to use for current device. Note that at the moment this is only used on iOS and partially on OSX. On OSX only allow* flags are used and they control remote notifications only. To control sound, alert and badge settings for local notifications on OSX, use Notifications settings in System Preferences. To setup push notifications for current device, provide permissions required, as well as register categories of notifications you want to support. Each category needs to have a unique identifier and it can optionally have multiple actions. Each action also needs to have a unique identifier. The example setup may look as follows: @code using Action = PushNotifications::Settings::Action; using Category = PushNotifications::Settings::Category; Action okAction; okAction.identifier = "okAction"; okAction.title = "OK!"; okAction.style = Action::button; okAction.triggerInBackground = true; Action cancelAction; cancelAction.identifier = "cancelAction"; cancelAction.title = "Cancel"; cancelAction.style = Action::button; cancelAction.triggerInBackground = true; cancelAction.destructive = true; Action textAction; textAction.identifier = "textAction"; textAction.title = "Enter text"; textAction.style = Action::text; textAction.triggerInBackground = true; textAction.destructive = false; textAction.textInputButtonText = "Ok"; textAction.textInputPlaceholder = "Enter text..."; Category okCategory; okCategory.identifier = "okCategory"; okCategory.actions = { okAction }; Category okCancelCategory; okCancelCategory.identifier = "okCancelCategory"; okCancelCategory.actions = { okAction, cancelAction }; Category textCategory; textCategory.identifier = "textCategory"; textCategory.actions = { textAction }; textCategory.sendDismissAction = true; PushNotifications::Settings settings; settings.allowAlert = true; settings.allowBadge = true; settings.allowSound = true; settings.categories = { okCategory, okCancelCategory, textCategory }; @endcode */ struct Settings { using Action = Notification::Action; /** Describes a category of a notification. Each category has a unique identifier and a list of associated actions. Note that the OS may allow only a limited number of actions to be presented, so always present most important actions first. */ struct Category { juce::String identifier; /**< unique identifier */ juce::Array actions; /**< optional list of actions within this category */ bool sendDismissAction = false; /**< whether dismiss action will be sent to the app (from iOS 10 only) */ }; bool allowSound = false; /**< whether the app should play a sound upon notification */ bool allowAlert = false; /**< whether the app should present an alert upon notification */ bool allowBadge = false; /**< whether the app may badge its icon upon notification */ Array categories; /**< list of categories the app wants to support */ }; /** Initialises push notifications on current device with the settings provided. Call this on your application startup and on iOS the first time the application starts, a user will be presented with a permission request dialog to give push notifications permission. Once a user responds, Listener::notificationSettingsReceived() will be called so that you can check what permissions where actually granted. The listener callback will be called on each subsequent startup too (provided you called requestPermissionsWithSettings() on previous application run). This way you can check what are current push notifications permissions. Note that settings are currently only used on iOS. When calling on other platforms, Settings with no categories and all allow* flags set to true will be received in Listener::notificationSettingsReceived(). You can also call requestSettingsUsed() to explicitly ask for current settings. */ void requestPermissionsWithSettings (const Settings& settings); /** Sends an asynchronous request to retrieve current settings that are currently in use. These can be exactly the same as used in requestPermissionsWithSettings(), but depending on user's subsequent changes in OS settings, the actual current settings may be different (e.g. user might have later decided to disable sounds). Note that settings are currently only used on iOS and partially on OSX. On OSX, only allow* flags are used and they refer to remote notifications only. For local notifications, refer to System Preferences. When calling this function on other platforms, Settings with no categories and all allow* flags set to true will be received in Listener::notificationSettingsReceived(). */ void requestSettingsUsed(); //============================================================================== /** Android API level 26 or higher only: Represents notification channel through which notifications will be sent. Starting from Android API level 26, you should call setupChannels() at the start of your application, before posting any notifications. Then, when sending notifications, assign a channel to each created notification. */ struct Channel { String identifier; /**< Required: Unique channel identifier. */ String name; /**< Required: User facing name of the channel. */ /** Controls how interruptive the notification posted on this channel are. */ enum Importance { none, min, low, normal, high, max }; Importance importance = normal; /**< Required. */ Notification::LockScreenAppearance lockScreenAppearance = Notification::showPartially; /**< Optional. */ String description; /**< Optional: user visible description of the channel. */ String groupId; /**< Required: group this channel belongs to (see ChannelGroup). */ Colour ledColour; /**< Optional: sets the led colour for notifications in this channel. */ bool bypassDoNotDisturb = false; /**< Optional: true if notifications in this channel can bypass do not disturb setting. */ bool canShowBadge = false; /**< Optional: true if notifications in this channel can show badges in a Launcher application. */ bool enableLights = false; /**< Optional: true if notifications in this channel should show lights (subject to hardware support). */ bool enableVibration = false; /**< Optional: true if notifications in this channel should trigger vibrations. */ URL soundToPlay; /**< Optional: sound to play in this channel. See Notification::soundToPlay for more info. */ Array vibrationPattern; /**< Optional: vibration pattern for this channel. See Notification::vibrationPattern for more info. */ }; /** Android API level 26 or higher only: represents a channel group. This allows for visual grouping of corresponding channels in notification settings presented to the user. At least one channel group has to be specified before notifications can be sent. */ struct ChannelGroup { String identifier; /**< Required: Unique channel group identifier. */ String name; /**< Required: User visible name of the channel group. */ }; /** Android API level 26 or higher only: configures notification channel groups and channels to be used in the app. These have to be setup before notifications can be sent on Android API level 26 or higher. */ void setupChannels (const Array& groups, const Array& channels); //============================================================================== /** iOS only: sends an asynchronous request to retrieve a list of notifications that were scheduled and not yet delivered. When the list is retrieved, Listener::pendingLocalNotificationsListReceived() will be called. */ void getPendingLocalNotifications() const; /** Unschedules a pending local notification with a given identifier. Available from iOS 10. */ void removePendingLocalNotification (const String& identifier); /** Unschedules all pending local notifications. iOS only. */ void removeAllPendingLocalNotifications(); //============================================================================== /** Checks whether notifications are enabled for given application. On iOS and OSX this will always return true, use requestSettingsUsed() instead. */ bool areNotificationsEnabled() const; /** On iOS as well as on Android, sends a local notification. On Android and iOS 10 or above, this will refresh an existing notification if the same identifier is used as in a notification that was already sent and not yet responded by a user. */ void sendLocalNotification (const Notification& notification); /** Sends a request for a list of notifications delivered. Such notifications are visible in the notification area on the device and they are still waiting for user action/response. When the request is finished Listener::deliveredNotificationsListReceived() will be called. On iOS, iOS version 10 or higher is required. On Android, API level 18 or higher is required. For unsupported platforms, Listener::deliveredNotificationsListReceived() will return an empty array. */ void getDeliveredNotifications() const; /** Removes a previously delivered notification. This can be useful for instance when the information in the notification becomes obsolete. */ void removeDeliveredNotification (const String& identifier); /** Removes all notifications that were delivered. */ void removeAllDeliveredNotifications(); //============================================================================== /** Retrieves current device token. Note, it is not a good idea to cache this token because it may change in the meantime. Always call this method to get the current token value. */ String getDeviceToken() const; /** Android only: allows to subscribe to messages from a specific topic. So you could for instance subscribe this device to all "sports" topic messages to receive any remote notifications that have "sports" topic set. Refer to Firebase documentation for how to send topic messages. */ void subscribeToTopic (const String& topic); /** Android only: allows to remove a topic subscription that was previously added with subscribeToTopic(). */ void unsubscribeFromTopic (const String& topic); /** Android only: sends an upstream message to your app server. The server must implement XMPP Connection Server protocol (refer to Firebase documentation). @param serverSenderId Represents the sender. Consult your Firebase project settings to retrieve the sender id. @param collapseKey Remote messages with the same collapse key that were not yet delivered will be collapsed into one, with the newest message replacing all the previous ones. Note that there may be a limit of maximum collapse keys used at the same time and beyond the limit (refer to Firebase documentation) it is not guaranteed which keys will be in use by the server. @param messageId A unique message ID. Used in error callbacks and debugging. @param messageType Message type. @param timeToLive TTL in seconds. If 0, the message sending will be attempted immediately and it will be dropped if the device is not connected. Otherwise, the message will be queued for the period specified. @param additionalData Collection of key-value pairs to be used as an additional data for the message. */ void sendUpstreamMessage (const String& serverSenderId, const String& collapseKey, const String& messageId, const String& messageType, int timeToLive, const StringPairArray& additionalData); //============================================================================== /** Register a listener (ideally on application startup) to receive information about notifications received and any callbacks to async functions called. */ struct Listener { virtual ~Listener() = default; /** This callback will be called after you call requestSettingsUsed() or requestPermissionsWithSettings(). Note that settings are currently only used on iOS. When called on other platforms, Settings with no categories and all allow flags set to true will be received in Listener::notificationSettingsReceived(). */ virtual void notificationSettingsReceived (const Settings& settings) { ignoreUnused (settings); } /** Called when the list of pending notifications, requested by calling getPendingLocalNotifications() is returned. iOS 10 or above only. */ virtual void pendingLocalNotificationsListReceived (const Array& notifications) { ignoreUnused (notifications); } /** This can be called in multiple different situations, depending on the OS and the situation. On pre iOS 10 device it will be called when a user presses on a notification or when a notification was received when the app was in the foreground already. On iOS 10 it will be called when a user presses on a notification Note: On Android, if remote notification was received while the app was in the background and then user pressed on it, the notification object received in this callback will contain only "properties" member set. Hence, if you want to know what was the notification title, content etc, you need to set them as additional properties, so that you will be able to restore them from "properties" dictionary. Note you can receive this callback on startup, if the application was launched from a notification. */ virtual void handleNotification (bool isLocalNotification, const Notification& notification) { ignoreUnused (isLocalNotification); ignoreUnused (notification); } /** This can be called when a user performs some action on the notification such as pressing on an action button or responding with a text input. Note that pressing on a notification area, i.e. not on an action button is not considered to be an action, and hence receivedNotification() will be called in that case. Note you can receive this callback on startup, if the application was launched from a notification's action. @param isLocalNotification If the notification is local @param notification The notification @param actionIdentifier A String identifying the action @param optionalResponse Text response a user inputs for notifications with a text input. Empty for notifications without a text input option. */ virtual void handleNotificationAction (bool isLocalNotification, const Notification& notification, const String& actionIdentifier, const String& optionalResponse) { ignoreUnused (isLocalNotification); ignoreUnused (notification); ignoreUnused (actionIdentifier); ignoreUnused (optionalResponse); } /** For iOS10 and Android, this can be also called when a user dismissed the notification before responding to it. */ virtual void localNotificationDismissedByUser (const Notification& notification) { ignoreUnused (notification); } /** Called after getDeliveredNotifications() request is fulfilled. Returns notifications that are visible in the notification area on the device and that are still waiting for a user action/response. On iOS, iOS version 10 or higher is required. On Android, API level 18 or higher is required. For unsupported platforms, an empty array will be returned. */ virtual void deliveredNotificationsListReceived (const Array& notifications) { ignoreUnused (notifications); } /** Called whenever a token gets refreshed. You should monitor any token updates, because only the last token that is assigned to device is valid and can be used. */ virtual void deviceTokenRefreshed (const String& token) { ignoreUnused (token); } /** Called when Firebase Cloud Messaging server deletes pending messages. This can happen when 1) too many messages were sent to the server (hint: use collapsible messages). 2) the devices hasn't been online in a long time (refer to Firebase documentation for the maximum time a message can be stored on FCM before expiring). */ virtual void remoteNotificationsDeleted() {} /** Called when an upstream message sent with PushNotifications::sendUpstreamMessage() has been sent successfully. Bear in mind that in may take several minutes or more to receive this callback. */ virtual void upstreamMessageSent (const String& messageId) { ignoreUnused (messageId); } /** Called when there was an error sending an upstream message with PushNotifications::sendUpstreamMessage(). Bear in mind that in may take several minutes or more to receive this callback. */ virtual void upstreamMessageSendingError (const String& messageId, const String& error) { ignoreUnused (messageId); ignoreUnused (error); } }; void addListener (Listener* l); void removeListener (Listener* l); private: PushNotifications(); ~PushNotifications() override; ListenerList listeners; #if JUCE_ANDROID friend bool juce_handleNotificationIntent (void*); friend struct JuceFirebaseInstanceIdService; friend struct JuceFirebaseMessagingService; #endif #if JUCE_PUSH_NOTIFICATIONS struct Pimpl; friend struct Pimpl; std::unique_ptr pimpl; #endif }; } // namespace juce