/* ============================================================================== This file is part of the JUCE 7 technical preview. Copyright (c) 2022 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For the technical preview this file cannot be licensed commercially. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \ METHOD (enableLights, "enableLights", "(Z)V") \ METHOD (enableVibration, "enableVibration", "(Z)V") \ METHOD (setBypassDnd, "setBypassDnd", "(Z)V") \ METHOD (setDescription, "setDescription", "(Ljava/lang/String;)V") \ METHOD (setGroup, "setGroup", "(Ljava/lang/String;)V") \ METHOD (setImportance, "setImportance", "(I)V") \ METHOD (setLightColor, "setLightColor", "(I)V") \ METHOD (setLockscreenVisibility, "setLockscreenVisibility", "(I)V") \ METHOD (setShowBadge, "setShowBadge", "(Z)V") \ METHOD (setSound, "setSound", "(Landroid/net/Uri;Landroid/media/AudioAttributes;)V") \ METHOD (setVibrationPattern, "setVibrationPattern", "([J)V") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationChannel, "android/app/NotificationChannel", 26) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;Ljava/lang/CharSequence;)V") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationChannelGroup, "android/app/NotificationChannelGroup", 26) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ FIELD (extras, "extras", "Landroid/os/Bundle;") DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidNotification, "android/app/Notification", 19) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addExtras, "addExtras", "(Landroid/os/Bundle;)Landroid/app/Notification$Action$Builder;") \ METHOD (addRemoteInput, "addRemoteInput", "(Landroid/app/RemoteInput;)Landroid/app/Notification$Action$Builder;") \ METHOD (constructor, "", "(ILjava/lang/CharSequence;Landroid/app/PendingIntent;)V") \ METHOD (build, "build", "()Landroid/app/Notification$Action;") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationActionBuilder, "android/app/Notification$Action$Builder", 20) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getNotification, "getNotification", "()Landroid/app/Notification;") \ METHOD (setAutoCancel, "setAutoCancel", "(Z)Landroid/app/Notification$Builder;") \ METHOD (setContentInfo, "setContentInfo", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \ METHOD (setContentIntent, "setContentIntent", "(Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \ METHOD (setContentText, "setContentText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \ METHOD (setContentTitle, "setContentTitle", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \ METHOD (setDefaults, "setDefaults", "(I)Landroid/app/Notification$Builder;") \ METHOD (setDeleteIntent, "setDeleteIntent", "(Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \ METHOD (setLargeIcon, "setLargeIcon", "(Landroid/graphics/Bitmap;)Landroid/app/Notification$Builder;") \ METHOD (setLights, "setLights", "(III)Landroid/app/Notification$Builder;") \ METHOD (setNumber, "setNumber", "(I)Landroid/app/Notification$Builder;") \ METHOD (setOngoing, "setOngoing", "(Z)Landroid/app/Notification$Builder;") \ METHOD (setOnlyAlertOnce, "setOnlyAlertOnce", "(Z)Landroid/app/Notification$Builder;") \ METHOD (setProgress, "setProgress", "(IIZ)Landroid/app/Notification$Builder;") \ METHOD (setSmallIcon, "setSmallIcon", "(I)Landroid/app/Notification$Builder;") \ METHOD (setSound, "setSound", "(Landroid/net/Uri;)Landroid/app/Notification$Builder;") \ METHOD (setTicker, "setTicker", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \ METHOD (setVibrate, "setVibrate", "([J)Landroid/app/Notification$Builder;") \ METHOD (setWhen, "setWhen", "(J)Landroid/app/Notification$Builder;") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderBase, "android/app/Notification$Builder", 11) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addAction, "addAction", "(ILjava/lang/CharSequence;Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \ METHOD (build, "build", "()Landroid/app/Notification;") \ METHOD (setPriority, "setPriority", "(I)Landroid/app/Notification$Builder;") \ METHOD (setSubText, "setSubText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \ METHOD (setUsesChronometer, "setUsesChronometer", "(Z)Landroid/app/Notification$Builder;") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi16, "android/app/Notification$Builder", 16) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (setShowWhen, "setShowWhen", "(Z)Landroid/app/Notification$Builder;") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi17, "android/app/Notification$Builder", 17) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addAction, "addAction", "(Landroid/app/Notification$Action;)Landroid/app/Notification$Builder;") \ METHOD (addExtras, "addExtras", "(Landroid/os/Bundle;)Landroid/app/Notification$Builder;") \ METHOD (setLocalOnly, "setLocalOnly", "(Z)Landroid/app/Notification$Builder;") \ METHOD (setGroup, "setGroup", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \ METHOD (setGroupSummary, "setGroupSummary", "(Z)Landroid/app/Notification$Builder;") \ METHOD (setSortKey, "setSortKey", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi20, "android/app/Notification$Builder", 20) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addPerson, "addPerson", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \ METHOD (setCategory, "setCategory", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \ METHOD (setColor, "setColor", "(I)Landroid/app/Notification$Builder;") \ METHOD (setPublicVersion, "setPublicVersion", "(Landroid/app/Notification;)Landroid/app/Notification$Builder;") \ METHOD (setVisibility, "setVisibility", "(I)Landroid/app/Notification$Builder;") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi21, "android/app/Notification$Builder", 21) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (setChronometerCountDown, "setChronometerCountDown", "(Z)Landroid/app/Notification$Builder;") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi24, "android/app/Notification$Builder", 24) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (setBadgeIconType, "setBadgeIconType", "(I)Landroid/app/Notification$Builder;") \ METHOD (setGroupAlertBehavior, "setGroupAlertBehavior", "(I)Landroid/app/Notification$Builder;") \ METHOD (setTimeoutAfter, "setTimeoutAfter", "(J)Landroid/app/Notification$Builder;") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi26, "android/app/Notification$Builder", 26) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (cancel, "cancel", "(Ljava/lang/String;I)V") \ METHOD (cancelAll, "cancelAll", "()V") \ METHOD (notify, "notify", "(Ljava/lang/String;ILandroid/app/Notification;)V") DECLARE_JNI_CLASS (NotificationManagerBase, "android/app/NotificationManager") #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getActiveNotifications, "getActiveNotifications", "()[Landroid/service/notification/StatusBarNotification;") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi23, "android/app/NotificationManager", 23) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (areNotificationsEnabled, "areNotificationsEnabled", "()Z") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi24, "android/app/NotificationManager", 24) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (createNotificationChannel, "createNotificationChannel", "(Landroid/app/NotificationChannel;)V") \ METHOD (createNotificationChannelGroup, "createNotificationChannelGroup", "(Landroid/app/NotificationChannelGroup;)V") DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi26, "android/app/NotificationManager", 26) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getResultsFromIntent, "getResultsFromIntent", "(Landroid/content/Intent;)Landroid/os/Bundle;") DECLARE_JNI_CLASS_WITH_MIN_SDK (RemoteInput, "android/app/RemoteInput", 20) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;)V") \ METHOD (build, "build", "()Landroid/app/RemoteInput;") \ METHOD (setAllowFreeFormInput, "setAllowFreeFormInput", "(Z)Landroid/app/RemoteInput$Builder;") \ METHOD (setChoices, "setChoices", "([Ljava/lang/CharSequence;)Landroid/app/RemoteInput$Builder;") \ METHOD (setLabel, "setLabel", "(Ljava/lang/CharSequence;)Landroid/app/RemoteInput$Builder;") DECLARE_JNI_CLASS_WITH_MIN_SDK (RemoteInputBuilder, "android/app/RemoteInput$Builder", 20) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getNotification, "getNotification", "()Landroid/app/Notification;") DECLARE_JNI_CLASS_WITH_MIN_SDK (StatusBarNotification, "android/service/notification/StatusBarNotification", 23) #undef JNI_CLASS_MEMBERS //============================================================================== #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME) #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getInstance, "getInstance", "()Lcom/google/firebase/iid/FirebaseInstanceId;") \ METHOD (getToken, "getToken", "()Ljava/lang/String;") DECLARE_JNI_CLASS (FirebaseInstanceId, "com/google/firebase/iid/FirebaseInstanceId") #undef JNI_CLASS_MEMBERS #endif #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getInstance, "getInstance", "()Lcom/google/firebase/messaging/FirebaseMessaging;") \ METHOD (send, "send", "(Lcom/google/firebase/messaging/RemoteMessage;)V") \ METHOD (subscribeToTopic, "subscribeToTopic", "(Ljava/lang/String;)Lcom/google/android/gms/tasks/Task;") \ METHOD (unsubscribeFromTopic, "unsubscribeFromTopic", "(Ljava/lang/String;)Lcom/google/android/gms/tasks/Task;") \ DECLARE_JNI_CLASS (FirebaseMessaging, "com/google/firebase/messaging/FirebaseMessaging") #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getCollapseKey, "getCollapseKey", "()Ljava/lang/String;") \ METHOD (getData, "getData", "()Ljava/util/Map;") \ METHOD (getFrom, "getFrom", "()Ljava/lang/String;") \ METHOD (getMessageId, "getMessageId", "()Ljava/lang/String;") \ METHOD (getMessageType, "getMessageType", "()Ljava/lang/String;") \ METHOD (getNotification, "getNotification", "()Lcom/google/firebase/messaging/RemoteMessage$Notification;") \ METHOD (getSentTime, "getSentTime", "()J") \ METHOD (getTo, "getTo", "()Ljava/lang/String;") \ METHOD (getTtl, "getTtl", "()I") DECLARE_JNI_CLASS (RemoteMessage, "com/google/firebase/messaging/RemoteMessage") #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addData, "addData", "(Ljava/lang/String;Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \ METHOD (build, "build", "()Lcom/google/firebase/messaging/RemoteMessage;") \ METHOD (constructor, "", "(Ljava/lang/String;)V") \ METHOD (setCollapseKey, "setCollapseKey", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \ METHOD (setMessageId, "setMessageId", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \ METHOD (setMessageType, "setMessageType", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \ METHOD (setTtl, "setTtl", "(I)Lcom/google/firebase/messaging/RemoteMessage$Builder;") DECLARE_JNI_CLASS (RemoteMessageBuilder, "com/google/firebase/messaging/RemoteMessage$Builder") #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getBody, "getBody", "()Ljava/lang/String;") \ METHOD (getBodyLocalizationArgs, "getBodyLocalizationArgs", "()[Ljava/lang/String;") \ METHOD (getBodyLocalizationKey, "getBodyLocalizationKey", "()Ljava/lang/String;") \ METHOD (getClickAction, "getClickAction", "()Ljava/lang/String;") \ METHOD (getColor, "getColor", "()Ljava/lang/String;") \ METHOD (getIcon, "getIcon", "()Ljava/lang/String;") \ METHOD (getLink, "getLink", "()Landroid/net/Uri;") \ METHOD (getSound, "getSound", "()Ljava/lang/String;") \ METHOD (getTag, "getTag", "()Ljava/lang/String;") \ METHOD (getTitle, "getTitle", "()Ljava/lang/String;") \ METHOD (getTitleLocalizationArgs, "getTitleLocalizationArgs", "()[Ljava/lang/String;") \ METHOD (getTitleLocalizationKey, "getTitleLocalizationKey", "()Ljava/lang/String;") DECLARE_JNI_CLASS (RemoteMessageNotification, "com/google/firebase/messaging/RemoteMessage$Notification") #undef JNI_CLASS_MEMBERS #endif //============================================================================== bool PushNotifications::Notification::isValid() const noexcept { bool isValidForPreApi26 = title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && icon.isNotEmpty(); bool apiAtLeast26 = (getAndroidSDKVersion() >= 26); if (apiAtLeast26) return isValidForPreApi26 && channelId.isNotEmpty(); return isValidForPreApi26; } //============================================================================== struct PushNotifications::Pimpl { Pimpl (PushNotifications& p) : owner (p) {} bool areNotificationsEnabled() const { if (getAndroidSDKVersion() >= 24) { auto* env = getEnv(); auto notificationManager = getNotificationManager(); if (notificationManager.get() != nullptr) return env->CallBooleanMethod (notificationManager, NotificationManagerApi24.areNotificationsEnabled); } return true; } //============================================================================== void sendLocalNotification (const PushNotifications::Notification& n) { // All required fields have to be setup! jassert (n.isValid()); auto* env = getEnv(); auto notificationManager = getNotificationManager(); if (notificationManager.get() != nullptr) { auto notification = juceNotificationToJavaNotification (n); auto tag = javaString (n.identifier); const int id = 0; env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.notify, tag.get(), id, notification.get()); } } void getDeliveredNotifications() const { if (getAndroidSDKVersion() >= 23) { auto* env = getEnv(); Array notifications; auto notificationManager = getNotificationManager(); jassert (notificationManager != nullptr); if (notificationManager.get() != nullptr) { auto statusBarNotifications = LocalRef ((jobjectArray)env->CallObjectMethod (notificationManager, NotificationManagerApi23.getActiveNotifications)); const int numNotifications = env->GetArrayLength (statusBarNotifications.get()); for (int i = 0; i < numNotifications; ++i) { auto statusBarNotification = LocalRef (env->GetObjectArrayElement (statusBarNotifications.get(), (jsize) i)); auto notification = LocalRef (env->CallObjectMethod (statusBarNotification, StatusBarNotification.getNotification)); notifications.add (javaNotificationToJuceNotification (notification)); } } owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifications); }); } else { // Not supported on this platform jassertfalse; owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); }); } } void notifyListenersAboutLocalNotification (const LocalRef& intent) { auto* env = getEnv(); LocalRef context (getMainActivity()); auto bundle = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getExtras)); const auto notification = localNotificationBundleToJuceNotification (bundle); auto packageName = juceString ((jstring) env->CallObjectMethod (context.get(), AndroidContext.getPackageName)); String notificationString = packageName + ".JUCE_NOTIFICATION."; String notificationButtonActionString = packageName + ".JUCE_NOTIFICATION_BUTTON_ACTION."; String notificationTextInputActionString = packageName + ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION."; auto actionString = juceString ((jstring) env->CallObjectMethod (intent, AndroidIntent.getAction)); if (actionString.contains (notificationString)) { owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, notification); }); } else if (actionString.contains (notificationButtonActionString)) { auto prefix = notificationButtonActionString + notification.identifier + "."; auto actionTitle = actionString.fromLastOccurrenceOf (prefix, false, false) // skip prefix .fromFirstOccurrenceOf (".", false, false); // skip action index owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, notification, actionTitle, {}); }); } else if (getAndroidSDKVersion() >= 20 && actionString.contains (notificationTextInputActionString)) { auto prefix = notificationTextInputActionString + notification.identifier + "."; auto actionTitle = actionString.fromLastOccurrenceOf (prefix, false, false) // skip prefix .fromFirstOccurrenceOf (".", false, false); // skip action index auto actionIndex = actionString.fromLastOccurrenceOf (prefix, false, false).upToFirstOccurrenceOf (".", false, false); auto resultKeyString = javaString (actionTitle + actionIndex); auto remoteInputResult = LocalRef (env->CallStaticObjectMethod (RemoteInput, RemoteInput.getResultsFromIntent, intent.get())); String responseString; if (remoteInputResult.get() == nullptr) { auto charSequence = LocalRef (env->CallObjectMethod (remoteInputResult, AndroidBundle.getCharSequence, resultKeyString.get())); auto responseStringRef = LocalRef ((jstring) env->CallObjectMethod (charSequence, JavaCharSequence.toString)); responseString = juceString (responseStringRef.get()); } owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, notification, actionTitle, responseString); }); } } void notifyListenersAboutLocalNotificationDeleted (const LocalRef& intent) { auto* env = getEnv(); auto bundle = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getExtras)); auto notification = localNotificationBundleToJuceNotification (bundle); owner.listeners.call ([&] (Listener& l) { l.localNotificationDismissedByUser (notification); }); } void removeAllDeliveredNotifications() { auto* env = getEnv(); auto notificationManager = getNotificationManager(); if (notificationManager.get() != nullptr) env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.cancelAll); } void removeDeliveredNotification (const String& identifier) { auto* env = getEnv(); auto notificationManager = getNotificationManager(); if (notificationManager.get() != nullptr) { auto tag = javaString (identifier); const int id = 0; env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.cancel, tag.get(), id); } } //============================================================================== String getDeviceToken() const { #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME) auto* env = getEnv(); auto instanceId = LocalRef (env->CallStaticObjectMethod (FirebaseInstanceId, FirebaseInstanceId.getInstance)); return juceString ((jstring) env->CallObjectMethod (instanceId, FirebaseInstanceId.getToken)); #else return {}; #endif } void notifyListenersTokenRefreshed (const String& token) { #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME) MessageManager::callAsync ([this, token] { owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (token); }); }); #else ignoreUnused (token); #endif } //============================================================================== void subscribeToTopic (const String& topic) { #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) auto* env = getEnv(); auto firebaseMessaging = LocalRef (env->CallStaticObjectMethod (FirebaseMessaging, FirebaseMessaging.getInstance)); env->CallObjectMethod (firebaseMessaging, FirebaseMessaging.subscribeToTopic, javaString (topic).get()); #else ignoreUnused (topic); #endif } void unsubscribeFromTopic (const String& topic) { #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) auto* env = getEnv(); auto firebaseMessaging = LocalRef (env->CallStaticObjectMethod (FirebaseMessaging, FirebaseMessaging.getInstance)); env->CallObjectMethod (firebaseMessaging, FirebaseMessaging.unsubscribeFromTopic, javaString (topic).get()); #else ignoreUnused (topic); #endif } void sendUpstreamMessage (const String& serverSenderId, const String& collapseKey, const String& messageId, const String& messageType, int timeToLive, const StringPairArray& additionalData) { #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) auto* env = getEnv(); auto messageBuilder = LocalRef (env->NewObject (RemoteMessageBuilder, RemoteMessageBuilder.constructor, javaString (serverSenderId + "@gcm_googleapis.com").get())); env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setCollapseKey, javaString (collapseKey).get()); env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setMessageId, javaString (messageId).get()); env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setMessageType, javaString (messageType).get()); env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setTtl, timeToLive); auto keys = additionalData.getAllKeys(); for (const auto& key : keys) env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.addData, javaString (key).get(), javaString (additionalData[key]).get()); auto message = LocalRef (env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.build)); auto firebaseMessaging = LocalRef (env->CallStaticObjectMethod (FirebaseMessaging, FirebaseMessaging.getInstance)); env->CallVoidMethod (firebaseMessaging, FirebaseMessaging.send, message.get()); #else ignoreUnused (serverSenderId, collapseKey, messageId, messageType); ignoreUnused (timeToLive, additionalData); #endif } void notifyListenersAboutRemoteNotificationFromSystemTray (const LocalRef& intent) { #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) auto* env = getEnv(); auto bundle = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getExtras)); auto notification = remoteNotificationBundleToJuceNotification (bundle); owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, notification); }); #else ignoreUnused (intent); #endif } void notifyListenersAboutRemoteNotificationFromService (const LocalRef& remoteNotification) { #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) GlobalRef rn (remoteNotification); MessageManager::callAsync ([this, rn] { auto notification = firebaseRemoteNotificationToJuceNotification (rn.get()); owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, notification); }); }); #else ignoreUnused (remoteNotification); #endif } void notifyListenersAboutRemoteNotificationsDeleted() { #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) MessageManager::callAsync ([this] { owner.listeners.call ([] (Listener& l) { l.remoteNotificationsDeleted(); }); }); #endif } void notifyListenersAboutUpstreamMessageSent (const LocalRef& messageId) { #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) GlobalRef mid (LocalRef(messageId.get())); MessageManager::callAsync ([this, mid] { auto midString = juceString ((jstring) mid.get()); owner.listeners.call ([&] (Listener& l) { l.upstreamMessageSent (midString); }); }); #else ignoreUnused (messageId); #endif } void notifyListenersAboutUpstreamMessageSendingError (const LocalRef& messageId, const LocalRef& error) { #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) GlobalRef mid (LocalRef(messageId.get())), e (LocalRef(error.get())); MessageManager::callAsync ([this, mid, e] { auto midString = juceString ((jstring) mid.get()); auto eString = juceString ((jstring) e.get()); owner.listeners.call ([&] (Listener& l) { l.upstreamMessageSendingError (midString, eString); }); }); #else ignoreUnused (messageId, error); #endif } static LocalRef getNotificationManager() { auto* env = getEnv(); LocalRef context (getMainActivity()); return LocalRef (env->CallObjectMethod (context.get(), AndroidContext.getSystemService, javaString ("notification").get())); } static LocalRef juceNotificationToJavaNotification (const PushNotifications::Notification& n) { auto* env = getEnv(); auto notificationBuilder = createNotificationBuilder (n); setupRequiredFields (n, notificationBuilder); setupOptionalFields (n, notificationBuilder); if (n.actions.size() > 0) setupActions (n, notificationBuilder); if (getAndroidSDKVersion() >= 16) return LocalRef (env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.build)); return LocalRef (env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.getNotification)); } static LocalRef createNotificationBuilder (const PushNotifications::Notification& n) { auto* env = getEnv(); LocalRef context (getMainActivity()); jclass builderClass = env->FindClass ("android/app/Notification$Builder"); jassert (builderClass != nullptr); if (builderClass == nullptr) return LocalRef (nullptr); jmethodID builderConstructor = nullptr; const bool apiAtLeast26 = (getAndroidSDKVersion() >= 26); if (apiAtLeast26) builderConstructor = env->GetMethodID (builderClass, "", "(Landroid/content/Context;Ljava/lang/String;)V"); else builderConstructor = env->GetMethodID (builderClass, "", "(Landroid/content/Context;)V"); jassert (builderConstructor != nullptr); if (builderConstructor == nullptr) return LocalRef (nullptr); if (apiAtLeast26) return LocalRef (env->NewObject (builderClass, builderConstructor, context.get(), javaString (n.channelId).get())); return LocalRef (env->NewObject (builderClass, builderConstructor, context.get())); } static void setupRequiredFields (const PushNotifications::Notification& n, LocalRef& notificationBuilder) { auto* env = getEnv(); LocalRef context (getMainActivity()); auto activityClass = LocalRef (env->CallObjectMethod (context.get(), JavaObject.getClass)); auto notifyIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get())); auto packageNameString = LocalRef ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); auto actionStringSuffix = javaString (".JUCE_NOTIFICATION." + n.identifier); auto actionString = LocalRef ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get())); env->CallObjectMethod (notifyIntent, AndroidIntent.setAction, actionString.get()); // Packaging entire notification into extras bundle here, so that we can retrieve all the details later on env->CallObjectMethod (notifyIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get()); auto notifyPendingIntent = LocalRef (env->CallStaticObjectMethod (AndroidPendingIntent, AndroidPendingIntent.getActivity, context.get(), 1002, notifyIntent.get(), 0)); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentTitle, javaString (n.title).get()); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentText, javaString (n.body).get()); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentIntent, notifyPendingIntent.get()); auto resources = LocalRef (env->CallObjectMethod (context.get(), AndroidContext.getResources)); const int iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(), javaString ("raw").get(), packageNameString.get()); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setSmallIcon, iconId); if (getAndroidSDKVersion() >= 21 && n.publicVersion != nullptr) { // Public version of a notification is not expected to have another public one! jassert (n.publicVersion->publicVersion == nullptr); auto publicNotificationBuilder = createNotificationBuilder (n); setupRequiredFields (*n.publicVersion, publicNotificationBuilder); setupOptionalFields (*n.publicVersion, publicNotificationBuilder); auto publicVersion = LocalRef (env->CallObjectMethod (publicNotificationBuilder, NotificationBuilderApi16.build)); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setPublicVersion, publicVersion.get()); } } static LocalRef juceNotificationToBundle (const PushNotifications::Notification& n) { auto* env = getEnv(); auto bundle = LocalRef (env->NewObject (AndroidBundle, AndroidBundle.constructor)); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("identifier") .get(), javaString (n.identifier).get()); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("title") .get(), javaString (n.title).get()); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("body") .get(), javaString (n.body).get()); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("subtitle") .get(), javaString (n.subtitle).get()); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("badgeNumber") .get(), n.badgeNumber); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("soundToPlay") .get(), javaString (n.soundToPlay.toString (true)).get()); env->CallVoidMethod (bundle, AndroidBundle.putBundle, javaString ("properties") .get(), varToBundleWithPropertiesString (n.properties).get()); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("icon") .get(), javaString (n.icon).get()); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("channelId") .get(), javaString (n.channelId).get()); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("tickerText") .get(), javaString (n.tickerText).get()); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("progressMax") .get(), n.progress.max); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("progressCurrent") .get(), n.progress.current); env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("progressIndeterminate") .get(), n.progress.indeterminate); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("person") .get(), javaString (n.person).get()); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("type") .get(), n.type); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("priority") .get(), n.priority); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("lockScreenAppearance") .get(), n.lockScreenAppearance); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("groupId") .get(), javaString (n.groupId).get()); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("groupSortKey") .get(), javaString (n.groupSortKey).get()); env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("groupSummary") .get(), n.groupSummary); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("accentColour") .get(), n.accentColour.getARGB()); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("ledColour") .get(), n.ledColour.getARGB()); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("ledBlinkPatternMsToBeOn") .get(), n.ledBlinkPattern.msToBeOn); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("ledBlinkPatternMsToBeOff").get(), n.ledBlinkPattern.msToBeOff); env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("shouldAutoCancel") .get(), n.shouldAutoCancel); env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("localOnly") .get(), n.localOnly); env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("ongoing") .get(), n.ongoing); env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("alertOnlyOnce") .get(), n.alertOnlyOnce); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("timestampVisibility") .get(), n.timestampVisibility); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("badgeIconType") .get(), n.badgeIconType); env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("groupAlertBehaviour") .get(), n.groupAlertBehaviour); env->CallVoidMethod (bundle, AndroidBundle.putLong, javaString ("timeoutAfterMs") .get(), (jlong)n.timeoutAfterMs); const int size = n.vibrationPattern.size(); if (size > 0) { auto array = LocalRef (env->NewLongArray (size)); jlong* elements = env->GetLongArrayElements (array, nullptr); for (int i = 0; i < size; ++i) elements[i] = (jlong) n.vibrationPattern[i]; env->SetLongArrayRegion (array, 0, size, elements); env->CallVoidMethod (bundle, AndroidBundle.putLongArray, javaString ("vibrationPattern").get(), array.get()); } return bundle; } static void setupOptionalFields (const PushNotifications::Notification& n, LocalRef& notificationBuilder) { auto* env = getEnv(); if (n.subtitle.isNotEmpty()) env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentInfo, javaString (n.subtitle).get()); auto soundName = n.soundToPlay.toString (true); if (soundName == "default_os_sound") { const int playDefaultSound = 1; env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDefaults, playDefaultSound); } else if (! soundName.isEmpty()) { env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setSound, juceUrlToAndroidUri (n.soundToPlay).get()); } if (n.largeIcon.isValid()) env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setLargeIcon, imagetoJavaBitmap (n.largeIcon).get()); if (n.tickerText.isNotEmpty()) env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setTicker, javaString (n.tickerText).get()); if (n.ledColour != Colour()) { env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setLights, n.ledColour.getARGB(), n.ledBlinkPattern.msToBeOn, n.ledBlinkPattern.msToBeOff); } if (! n.vibrationPattern.isEmpty()) { const int size = n.vibrationPattern.size(); if (size > 0) { auto array = LocalRef (env->NewLongArray (size)); jlong* elements = env->GetLongArrayElements (array, nullptr); for (int i = 0; i < size; ++i) elements[i] = (jlong) n.vibrationPattern[i]; env->SetLongArrayRegion (array, 0, size, elements); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setVibrate, array.get()); } } env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setProgress, n.progress.max, n.progress.current, n.progress.indeterminate); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setNumber, n.badgeNumber); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setAutoCancel, n.shouldAutoCancel); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setOngoing, n.ongoing); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setOnlyAlertOnce, n.alertOnlyOnce); if (getAndroidSDKVersion() >= 16) { if (n.subtitle.isNotEmpty()) env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setSubText, javaString (n.subtitle).get()); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setPriority, n.priority); if (getAndroidSDKVersion() < 24) { const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer; env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer); } } if (getAndroidSDKVersion() >= 17) { const bool showTimeStamp = n.timestampVisibility != PushNotifications::Notification::off; env->CallObjectMethod (notificationBuilder, NotificationBuilderApi17.setShowWhen, showTimeStamp); } if (getAndroidSDKVersion() >= 20) { if (n.groupId.isNotEmpty()) { env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroup, javaString (n.groupId).get()); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroupSummary, n.groupSummary); } if (n.groupSortKey.isNotEmpty()) env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setSortKey, javaString (n.groupSortKey).get()); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setLocalOnly, n.localOnly); auto extras = LocalRef (env->NewObject (AndroidBundle, AndroidBundle.constructor)); env->CallVoidMethod (extras, AndroidBundle.putBundle, javaString ("notificationData").get(), juceNotificationToBundle (n).get()); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addExtras, extras.get()); } if (getAndroidSDKVersion() >= 21) { if (n.person.isNotEmpty()) env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.addPerson, javaString (n.person).get()); auto categoryString = typeToCategory (n.type); if (categoryString.isNotEmpty()) env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setCategory, javaString (categoryString).get()); if (n.accentColour != Colour()) env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setColor, n.accentColour.getARGB()); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setVisibility, n.lockScreenAppearance); } if (getAndroidSDKVersion() >= 24) { const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer; const bool useCountDownChronometer = n.timestampVisibility == PushNotifications::Notification::countDownChronometer; env->CallObjectMethod (notificationBuilder, NotificationBuilderApi24.setChronometerCountDown, useCountDownChronometer); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer | useCountDownChronometer); } if (getAndroidSDKVersion() >= 26) { env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setBadgeIconType, n.badgeIconType); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setGroupAlertBehavior, n.groupAlertBehaviour); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setTimeoutAfter, (jlong) n.timeoutAfterMs); } setupNotificationDeletedCallback (n, notificationBuilder); } static void setupNotificationDeletedCallback (const PushNotifications::Notification& n, LocalRef& notificationBuilder) { auto* env = getEnv(); LocalRef context (getMainActivity()); auto activityClass = LocalRef (env->CallObjectMethod (context.get(), JavaObject.getClass)); auto deleteIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get())); auto packageNameString = LocalRef ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); auto actionStringSuffix = javaString (".JUCE_NOTIFICATION_DELETED." + n.identifier); auto actionString = LocalRef ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get())); env->CallObjectMethod (deleteIntent, AndroidIntent.setAction, actionString.get()); env->CallObjectMethod (deleteIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get()); auto deletePendingIntent = LocalRef (env->CallStaticObjectMethod (AndroidPendingIntent, AndroidPendingIntent.getActivity, context.get(), 1002, deleteIntent.get(), 0)); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDeleteIntent, deletePendingIntent.get()); } static void setupActions (const PushNotifications::Notification& n, LocalRef& notificationBuilder) { if (getAndroidSDKVersion() < 16) return; auto* env = getEnv(); LocalRef context (getMainActivity()); int actionIndex = 0; for (const auto& action : n.actions) { auto activityClass = LocalRef (env->CallObjectMethod (context.get(), JavaObject.getClass)); auto notifyIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get())); const bool isTextStyle = action.style == PushNotifications::Notification::Action::text; auto packageNameString = LocalRef ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); const String notificationActionString = isTextStyle ? ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION." : ".JUCE_NOTIFICATION_BUTTON_ACTION."; auto actionStringSuffix = javaString (notificationActionString + n.identifier + "." + String (actionIndex) + "." + action.title); auto actionString = LocalRef ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get())); env->CallObjectMethod (notifyIntent, AndroidIntent.setAction, actionString.get()); // Packaging entire notification into extras bundle here, so that we can retrieve all the details later on env->CallObjectMethod (notifyIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get()); auto notifyPendingIntent = LocalRef (env->CallStaticObjectMethod (AndroidPendingIntent, AndroidPendingIntent.getActivity, context.get(), 1002, notifyIntent.get(), 0)); auto resources = LocalRef (env->CallObjectMethod (context.get(), AndroidContext.getResources)); int iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (action.icon).get(), javaString ("raw").get(), packageNameString.get()); if (iconId == 0) iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(), javaString ("raw").get(), packageNameString.get()); if (getAndroidSDKVersion() >= 20) { auto actionBuilder = LocalRef (env->NewObject (NotificationActionBuilder, NotificationActionBuilder.constructor, iconId, javaString (action.title).get(), notifyPendingIntent.get())); env->CallObjectMethod (actionBuilder, NotificationActionBuilder.addExtras, varToBundleWithPropertiesString (action.parameters).get()); if (isTextStyle) { auto resultKey = javaString (action.title + String (actionIndex)); auto remoteInputBuilder = LocalRef (env->NewObject (RemoteInputBuilder, RemoteInputBuilder.constructor, resultKey.get())); if (! action.textInputPlaceholder.isEmpty()) env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setLabel, javaString (action.textInputPlaceholder).get()); if (! action.allowedResponses.isEmpty()) { env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setAllowFreeFormInput, false); const int size = action.allowedResponses.size(); auto array = LocalRef (env->NewObjectArray (size, env->FindClass ("java/lang/String"), nullptr)); for (int i = 0; i < size; ++i) { const auto& response = action.allowedResponses[i]; auto responseString = javaString (response); env->SetObjectArrayElement (array, i, responseString.get()); } env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setChoices, array.get()); } env->CallObjectMethod (actionBuilder, NotificationActionBuilder.addRemoteInput, env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.build)); } env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addAction, env->CallObjectMethod (actionBuilder, NotificationActionBuilder.build)); } else { env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.addAction, iconId, javaString (action.title).get(), notifyPendingIntent.get()); } ++actionIndex; } } static LocalRef juceUrlToAndroidUri (const URL& url) { auto* env = getEnv(); LocalRef context (getMainActivity()); auto packageNameString = LocalRef ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); auto resources = LocalRef (env->CallObjectMethod (context.get(), AndroidContext.getResources)); const int id = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (url.toString (true)).get(), javaString ("raw").get(), packageNameString.get()); auto schemeString = javaString ("android.resource://"); auto resourceString = javaString ("/" + String (id)); auto uriString = LocalRef ((jstring) env->CallObjectMethod (schemeString, JavaString.concat, packageNameString.get())); uriString = LocalRef ((jstring) env->CallObjectMethod (uriString, JavaString.concat, resourceString.get())); return LocalRef (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, uriString.get())); } static LocalRef imagetoJavaBitmap (const Image& image) { auto* env = getEnv(); Image imageToUse = image.convertedToFormat (Image::PixelFormat::ARGB); auto bitmapConfig = LocalRef (env->CallStaticObjectMethod (AndroidBitmapConfig, AndroidBitmapConfig.valueOf, javaString ("ARGB_8888").get())); auto bitmap = LocalRef (env->CallStaticObjectMethod (AndroidBitmap, AndroidBitmap.createBitmap, image.getWidth(), image.getHeight(), bitmapConfig.get())); for (int i = 0; i < image.getWidth(); ++i) for (int j = 0; j < image.getHeight(); ++j) env->CallVoidMethod (bitmap.get(), AndroidBitmap.setPixel, i, j, image.getPixelAt (i, j).getARGB()); return bitmap; } static String typeToCategory (PushNotifications::Notification::Type t) { switch (t) { case PushNotifications::Notification::unspecified: return {}; case PushNotifications::Notification::alarm: return "alarm"; case PushNotifications::Notification::call: return "call"; case PushNotifications::Notification::email: return "email"; case PushNotifications::Notification::error: return "err"; case PushNotifications::Notification::event: return "event"; case PushNotifications::Notification::message: return "msg"; case PushNotifications::Notification::taskProgress: return "progress"; case PushNotifications::Notification::promo: return "promo"; case PushNotifications::Notification::recommendation: return "recommendation"; case PushNotifications::Notification::reminder: return "reminder"; case PushNotifications::Notification::service: return "service"; case PushNotifications::Notification::social: return "social"; case PushNotifications::Notification::status: return "status"; case PushNotifications::Notification::system: return "sys"; case PushNotifications::Notification::transport: return "transport"; } return {}; } static LocalRef varToBundleWithPropertiesString (const var& varToParse) { auto* env = getEnv(); auto bundle = LocalRef (env->NewObject (AndroidBundle, AndroidBundle.constructor)); env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("properties").get(), javaString (JSON::toString (varToParse, false)).get()); return bundle; } // Gets "properties" var from bundle. static var bundleWithPropertiesStringToVar (const LocalRef& bundle) { auto* env = getEnv(); auto varString = LocalRef ((jstring)env->CallObjectMethod (bundle, AndroidBundle.getString, javaString ("properties").get())); var resultVar; JSON::parse (juceString (varString.get()), resultVar); // Note: We are not checking if result of parsing was okay, because there may be no properties set at all. return resultVar; } // Reverse of juceNotificationToBundle(). static PushNotifications::Notification localNotificationBundleToJuceNotification (const LocalRef& bundle) { auto* env = getEnv(); PushNotifications::Notification n; if (bundle.get() != nullptr) { n.identifier = getStringFromBundle (env, "identifier", bundle); n.title = getStringFromBundle (env, "title", bundle); n.body = getStringFromBundle (env, "body", bundle); n.subtitle = getStringFromBundle (env, "subtitle", bundle); n.badgeNumber = getIntFromBundle (env, "badgeNumber", bundle); n.soundToPlay = URL (getStringFromBundle (env, "soundToPlay", bundle)); n.properties = getPropertiesVarFromBundle (env, "properties", bundle); n.tickerText = getStringFromBundle (env, "tickerText", bundle); n.icon = getStringFromBundle (env, "icon", bundle); n.channelId = getStringFromBundle (env, "channelId", bundle); PushNotifications::Notification::Progress progress; progress.max = getIntFromBundle (env, "progressMax", bundle); progress.current = getIntFromBundle (env, "progressCurrent", bundle); progress.indeterminate = getBoolFromBundle (env, "progressIndeterminate", bundle); n.progress = progress; n.person = getStringFromBundle (env, "person", bundle); n.type = (PushNotifications::Notification::Type) getIntFromBundle (env, "type", bundle); n.priority = (PushNotifications::Notification::Priority) getIntFromBundle (env, "priority", bundle); n.lockScreenAppearance = (PushNotifications::Notification::LockScreenAppearance) getIntFromBundle (env, "lockScreenAppearance", bundle); n.groupId = getStringFromBundle (env, "groupId", bundle); n.groupSortKey = getStringFromBundle (env, "groupSortKey", bundle); n.groupSummary = getBoolFromBundle (env, "groupSummary", bundle); n.accentColour = Colour ((uint32) getIntFromBundle (env, "accentColour", bundle)); n.ledColour = Colour ((uint32) getIntFromBundle (env, "ledColour", bundle)); PushNotifications::Notification::LedBlinkPattern ledBlinkPattern; ledBlinkPattern.msToBeOn = getIntFromBundle (env, "ledBlinkPatternMsToBeOn", bundle); ledBlinkPattern.msToBeOff = getIntFromBundle (env, "ledBlinkPatternMsToBeOff", bundle); n.ledBlinkPattern = ledBlinkPattern; n.vibrationPattern = getLongArrayFromBundle (env, "vibrationPattern", bundle); n.shouldAutoCancel = getBoolFromBundle (env, "shouldAutoCancel", bundle); n.localOnly = getBoolFromBundle (env, "localOnly", bundle); n.ongoing = getBoolFromBundle (env, "ongoing", bundle); n.alertOnlyOnce = getBoolFromBundle (env, "alertOnlyOnce", bundle); n.timestampVisibility = (PushNotifications::Notification::TimestampVisibility) getIntFromBundle (env, "timestampVisibility", bundle); n.badgeIconType = (PushNotifications::Notification::BadgeIconType) getIntFromBundle (env, "badgeIconType", bundle); n.groupAlertBehaviour = (PushNotifications::Notification::GroupAlertBehaviour) getIntFromBundle (env, "groupAlertBehaviour", bundle); n.timeoutAfterMs = getLongFromBundle (env, "timeoutAfterMs", bundle); } return n; } static String getStringFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) { auto keyString = javaString (key); if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) { auto value = LocalRef ((jstring)env->CallObjectMethod (bundle, AndroidBundle.getString, keyString.get())); return juceString (value); } return {}; } static int getIntFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) { auto keyString = javaString (key); if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) return env->CallIntMethod (bundle, AndroidBundle.getInt, keyString.get()); return 0; } // Converting to int on purpose! static int getLongFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) { auto keyString = javaString (key); if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) return (int) env->CallLongMethod (bundle, AndroidBundle.getLong, keyString.get()); return 0; } static var getPropertiesVarFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) { auto keyString = javaString (key); if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) { auto value = LocalRef (env->CallObjectMethod (bundle, AndroidBundle.getBundle, keyString.get())); return bundleWithPropertiesStringToVar (value); } return {}; } static bool getBoolFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) { auto keyString = javaString (key); if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) return env->CallBooleanMethod (bundle, AndroidBundle.getBoolean, keyString.get()); return false; } static Array getLongArrayFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) { auto keyString = javaString (key); if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) { auto array = LocalRef ((jlongArray) env->CallObjectMethod (bundle, AndroidBundle.getLongArray, keyString.get())); const int size = env->GetArrayLength (array.get()); jlong* elements = env->GetLongArrayElements (array.get(), nullptr); Array resultArray; for (int i = 0; i < size; ++i) resultArray.add ((int) *elements++); return resultArray; } return {}; } static PushNotifications::Notification javaNotificationToJuceNotification (const LocalRef& notification) { if (getAndroidSDKVersion() < 20) return {}; auto* env = getEnv(); auto extras = LocalRef (env->GetObjectField (notification, AndroidNotification.extras)); auto notificationData = LocalRef (env->CallObjectMethod (extras, AndroidBundle.getBundle, javaString ("notificationData").get())); if (notificationData.get() != nullptr) return localNotificationBundleToJuceNotification (notificationData); return remoteNotificationBundleToJuceNotification (extras); } static PushNotifications::Notification remoteNotificationBundleToJuceNotification (const LocalRef& bundle) { // This will probably work only for remote notifications that get delivered to system tray PushNotifications::Notification n; n.properties = bundleToVar (bundle); return n; } static var bundleToVar (const LocalRef& bundle) { if (bundle.get() == nullptr) { auto* env = getEnv(); auto keySet = LocalRef (env->CallObjectMethod (bundle, AndroidBundle.keySet)); auto iterator = LocalRef (env->CallObjectMethod (keySet, JavaSet.iterator)); DynamicObject::Ptr dynamicObject = new DynamicObject(); for (;;) { if (! env->CallBooleanMethod (iterator, JavaIterator.hasNext)) break; auto key = LocalRef ((jstring) env->CallObjectMethod (iterator, JavaIterator.next)); auto object = LocalRef (env->CallObjectMethod (bundle, AndroidBundle.get, key.get())); if (object.get() != nullptr) { auto objectAsString = LocalRef ((jstring) env->CallObjectMethod (object, JavaObject.toString)); auto objectClass = LocalRef (env->CallObjectMethod (object, JavaObject.getClass)); auto classAsString = LocalRef ((jstring) env->CallObjectMethod (objectClass, JavaClass.getName)); // Note: It seems that Firebase delivers values as strings always, so this check is rather unnecessary, // at least until they change the behaviour. var value = juceString (classAsString) == "java.lang.Bundle" ? bundleToVar (object) : var (juceString (objectAsString.get())); dynamicObject->setProperty (juceString (key.get()), value); } else { dynamicObject->setProperty (juceString (key.get()), {}); } } return var (dynamicObject.get()); } return {}; } #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) static PushNotifications::Notification firebaseRemoteNotificationToJuceNotification (jobject remoteNotification) { auto* env = getEnv(); auto collapseKey = LocalRef ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getCollapseKey)); auto from = LocalRef ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getFrom)); auto messageId = LocalRef ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getMessageId)); auto messageType = LocalRef ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getMessageType)); auto to = LocalRef ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getTo)); auto notification = LocalRef (env->CallObjectMethod (remoteNotification, RemoteMessage.getNotification)); auto data = LocalRef (env->CallObjectMethod (remoteNotification, RemoteMessage.getData)); const int64 sentTime = env->CallLongMethod (remoteNotification, RemoteMessage.getSentTime); const int ttl = env->CallIntMethod (remoteNotification, RemoteMessage.getTtl); auto keySet = LocalRef (env->CallObjectMethod (data, JavaMap.keySet)); auto iterator = LocalRef (env->CallObjectMethod (keySet, JavaSet.iterator)); DynamicObject::Ptr dataDynamicObject = new DynamicObject(); for (;;) { if (! env->CallBooleanMethod (iterator, JavaIterator.hasNext)) break; auto key = LocalRef ((jstring) env->CallObjectMethod (iterator, JavaIterator.next)); auto value = LocalRef ((jstring) env->CallObjectMethod (data, JavaMap.get, key.get())); dataDynamicObject->setProperty (juceString (key.get()), juceString (value.get())); } var dataVar (dataDynamicObject.get()); DynamicObject::Ptr propertiesDynamicObject = new DynamicObject(); propertiesDynamicObject->setProperty ("collapseKey", juceString (collapseKey.get())); propertiesDynamicObject->setProperty ("from", juceString (from.get())); propertiesDynamicObject->setProperty ("messageId", juceString (messageId.get())); propertiesDynamicObject->setProperty ("messageType", juceString (messageType.get())); propertiesDynamicObject->setProperty ("to", juceString (to.get())); propertiesDynamicObject->setProperty ("sentTime", sentTime); propertiesDynamicObject->setProperty ("ttl", ttl); propertiesDynamicObject->setProperty ("data", dataVar); PushNotifications::Notification n; if (notification != 0) { auto body = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getBody)); auto bodyLocalizationKey = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getBodyLocalizationKey)); auto clickAction = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getClickAction)); auto color = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getColor)); auto icon = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getIcon)); auto sound = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getSound)); auto tag = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTag)); auto title = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTitle)); auto titleLocalizationKey = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTitleLocalizationKey)); auto link = LocalRef (env->CallObjectMethod (notification, RemoteMessageNotification.getLink)); auto bodyLocalizationArgs = LocalRef ((jobjectArray) env->CallObjectMethod (notification, RemoteMessageNotification.getBodyLocalizationArgs)); auto titleLocalizationArgs = LocalRef ((jobjectArray) env->CallObjectMethod (notification, RemoteMessageNotification.getTitleLocalizationArgs)); n.identifier = juceString (tag.get()); n.title = juceString (title.get()); n.body = juceString (body.get()); n.soundToPlay = URL (juceString (sound.get())); auto colourString = juceString (color.get()).substring (1); const uint8 r = (uint8) colourString.substring (0, 2).getIntValue(); const uint8 g = (uint8) colourString.substring (2, 4).getIntValue(); const uint8 b = (uint8) colourString.substring (4, 6).getIntValue(); n.accentColour = Colour (r, g, b); // Note: Ignoring the icon, because Firebase passes it as a string. propertiesDynamicObject->setProperty ("clickAction", juceString (clickAction.get())); propertiesDynamicObject->setProperty ("bodyLocalizationKey", juceString (bodyLocalizationKey.get())); propertiesDynamicObject->setProperty ("titleLocalizationKey", juceString (titleLocalizationKey.get())); propertiesDynamicObject->setProperty ("bodyLocalizationArgs", javaStringArrayToJuce (bodyLocalizationArgs)); propertiesDynamicObject->setProperty ("titleLocalizationArgs", javaStringArrayToJuce (titleLocalizationArgs)); propertiesDynamicObject->setProperty ("link", link.get() != nullptr ? juceString ((jstring) env->CallObjectMethod (link, AndroidUri.toString)) : String()); } n.properties = var (propertiesDynamicObject.get()); return n; } #endif void setupChannels (const Array& groups, const Array& channels) { if (getAndroidSDKVersion() < 26) return; auto* env = getEnv(); auto notificationManager = getNotificationManager(); jassert (notificationManager.get() != nullptr); if (notificationManager.get() == nullptr) return; for (const auto& g : groups) { // Channel group identifier and name have to be set. jassert (g.identifier.isNotEmpty() && g.name.isNotEmpty()); if (g.identifier.isNotEmpty() && g.name.isNotEmpty()) { auto group = LocalRef (env->NewObject (NotificationChannelGroup, NotificationChannelGroup.constructor, javaString (g.identifier).get(), javaString (g.name).get())); env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannelGroup, group.get()); } } for (const auto& c : channels) { // Channel identifier, name and group have to be set. jassert (c.identifier.isNotEmpty() && c.name.isNotEmpty() && c.groupId.isNotEmpty()); if (c.identifier.isEmpty() || c.name.isEmpty() || c.groupId.isEmpty()) continue; auto channel = LocalRef (env->NewObject (NotificationChannel, NotificationChannel.constructor, javaString (c.identifier).get(), javaString (c.name).get(), c.importance)); env->CallVoidMethod (channel, NotificationChannel.enableLights, c.enableLights); env->CallVoidMethod (channel, NotificationChannel.enableVibration, c.enableVibration); env->CallVoidMethod (channel, NotificationChannel.setBypassDnd, c.bypassDoNotDisturb); env->CallVoidMethod (channel, NotificationChannel.setDescription, javaString (c.description).get()); env->CallVoidMethod (channel, NotificationChannel.setGroup, javaString (c.groupId).get()); env->CallVoidMethod (channel, NotificationChannel.setImportance, c.importance); env->CallVoidMethod (channel, NotificationChannel.setLightColor, c.ledColour.getARGB()); env->CallVoidMethod (channel, NotificationChannel.setLockscreenVisibility, c.lockScreenAppearance); env->CallVoidMethod (channel, NotificationChannel.setShowBadge, c.canShowBadge); const int size = c.vibrationPattern.size(); if (size > 0) { auto array = LocalRef (env->NewLongArray (size)); jlong* elements = env->GetLongArrayElements (array, nullptr); for (int i = 0; i < size; ++i) elements[i] = (jlong) c.vibrationPattern[i]; env->SetLongArrayRegion (array, 0, size, elements); env->CallVoidMethod (channel, NotificationChannel.setVibrationPattern, array.get()); env->CallVoidMethod (channel, NotificationChannel.enableVibration, c.enableVibration); } LocalRef builder (env->NewObject (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.constructor)); const int contentTypeSonification = 4; const int usageNotification = 5; env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.setContentType, contentTypeSonification); env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.setUsage, usageNotification); auto audioAttributes = LocalRef (env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.build)); env->CallVoidMethod (channel, NotificationChannel.setSound, juceUrlToAndroidUri (c.soundToPlay).get(), audioAttributes.get()); env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannel, channel.get()); } } void getPendingLocalNotifications() const {} void removePendingLocalNotification (const String&) {} void removeAllPendingLocalNotifications() {} static bool intentActionContainsAnyOf (jobject intent, const StringArray& strings, bool includePackageName) { auto* env = getEnv(); LocalRef context (getMainActivity()); String packageName = includePackageName ? juceString ((jstring) env->CallObjectMethod (context.get(), AndroidContext.getPackageName)) : String{}; String intentAction = juceString ((jstring) env->CallObjectMethod (intent, AndroidIntent.getAction)); for (const auto& string : strings) if (intentAction.contains (packageName + string)) return true; return false; } static bool isDeleteNotificationIntent (jobject intent) { return intentActionContainsAnyOf (intent, StringArray (".JUCE_NOTIFICATION_DELETED"), true); } static bool isLocalNotificationIntent (jobject intent) { return intentActionContainsAnyOf (intent, { ".JUCE_NOTIFICATION.", ".JUCE_NOTIFICATION_BUTTON_ACTION.", ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION." }, true); } static bool isRemoteNotificationIntent (jobject intent) { auto* env = getEnv(); auto categories = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getCategories)); int categoriesNum = categories != nullptr ? env->CallIntMethod (categories, JavaSet.size) : 0; if (categoriesNum == 0) return false; if (! env->CallBooleanMethod (categories, JavaSet.contains, javaString ("android.intent.category.LAUNCHER").get())) return false; if (! intentActionContainsAnyOf (intent, StringArray ("android.intent.action.MAIN"), false)) return false; auto extras = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getExtras)); if (extras == nullptr) return false; return env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.sent_time").get()) && env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.message_id").get()); } PushNotifications& owner; }; #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME) //============================================================================== struct JuceFirebaseInstanceIdService { #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ CALLBACK (tokenRefreshed, "firebaseInstanceIdTokenRefreshed", "(Ljava/lang/String;)V") DECLARE_JNI_CLASS (InstanceIdService, "com/rmsl/juce/JuceFirebaseInstanceIdService") #undef JNI_CLASS_MEMBERS static void JNICALL tokenRefreshed (JNIEnv*, jobject /*instanceIdService*/, void* token) { if (auto* instance = PushNotifications::getInstanceWithoutCreating()) instance->pimpl->notifyListenersTokenRefreshed (juceString (static_cast (token))); } }; JuceFirebaseInstanceIdService::InstanceIdService_Class JuceFirebaseInstanceIdService::InstanceIdService; #endif #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) //============================================================================== struct JuceFirebaseMessagingService { #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ CALLBACK (remoteNotificationReceived, "firebaseRemoteMessageReceived", "(Lcom/google/firebase/messaging/RemoteMessage;)V") \ CALLBACK (remoteMessagesDeleted, "firebaseRemoteMessagesDeleted", "()V") \ CALLBACK (remoteMessageSent, "firebaseRemoteMessageSent", "(Ljava/lang/String;)V") \ CALLBACK (remoteMessageSendError, "firebaseRemoteMessageSendError", "(Ljava/lang/String;Ljava/lang/String;)V") DECLARE_JNI_CLASS (MessagingService, "com/rmsl/juce/JuceFirebaseMessagingService") #undef JNI_CLASS_MEMBERS static void JNICALL remoteNotificationReceived (JNIEnv*, jobject /*messagingService*/, void* remoteMessage) { if (auto* instance = PushNotifications::getInstanceWithoutCreating()) instance->pimpl->notifyListenersAboutRemoteNotificationFromService (LocalRef (static_cast (remoteMessage))); } static void JNICALL remoteMessagesDeleted() { if (auto* instance = PushNotifications::getInstanceWithoutCreating()) instance->pimpl->notifyListenersAboutRemoteNotificationsDeleted(); } static void JNICALL remoteMessageSent (JNIEnv*, jobject /*messagingService*/, void* messageId) { if (auto* instance = PushNotifications::getInstanceWithoutCreating()) instance->pimpl->notifyListenersAboutUpstreamMessageSent (LocalRef (static_cast (messageId))); } static void JNICALL remoteMessageSendError (JNIEnv*, jobject /*messagingService*/, void* messageId, void* error) { if (auto* instance = PushNotifications::getInstanceWithoutCreating()) instance->pimpl->notifyListenersAboutUpstreamMessageSendingError (LocalRef (static_cast (messageId)), LocalRef (static_cast (error))); } }; JuceFirebaseMessagingService::MessagingService_Class JuceFirebaseMessagingService::MessagingService; #endif //============================================================================== bool juce_handleNotificationIntent (void* intent) { auto* instance = PushNotifications::getInstanceWithoutCreating(); if (PushNotifications::Pimpl::isDeleteNotificationIntent ((jobject) intent)) { if (instance) instance->pimpl->notifyListenersAboutLocalNotificationDeleted (LocalRef ((jobject) intent)); return true; } else if (PushNotifications::Pimpl::isLocalNotificationIntent ((jobject) intent)) { if (instance) instance->pimpl->notifyListenersAboutLocalNotification (LocalRef ((jobject) intent)); return true; } #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) else if (PushNotifications::Pimpl::isRemoteNotificationIntent ((jobject) intent)) { if (instance) instance->pimpl->notifyListenersAboutRemoteNotificationFromSystemTray (LocalRef ((jobject) intent)); return true; } #endif return false; } } // namespace juce