1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
30   METHOD (constructor,             "<init>",                  "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \
31   METHOD (enableLights,            "enableLights",            "(Z)V") \
32   METHOD (enableVibration,         "enableVibration",         "(Z)V") \
33   METHOD (setBypassDnd,            "setBypassDnd",            "(Z)V") \
34   METHOD (setDescription,          "setDescription",          "(Ljava/lang/String;)V") \
35   METHOD (setGroup,                "setGroup",                "(Ljava/lang/String;)V") \
36   METHOD (setImportance,           "setImportance",           "(I)V") \
37   METHOD (setLightColor,           "setLightColor",           "(I)V") \
38   METHOD (setLockscreenVisibility, "setLockscreenVisibility", "(I)V") \
39   METHOD (setShowBadge,            "setShowBadge",            "(Z)V") \
40   METHOD (setSound,                "setSound",                "(Landroid/net/Uri;Landroid/media/AudioAttributes;)V") \
41   METHOD (setVibrationPattern,     "setVibrationPattern",     "([J)V")
42 
43 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationChannel, "android/app/NotificationChannel", 26)
44 #undef JNI_CLASS_MEMBERS
45 
46 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
47   METHOD (constructor, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;)V")
48 
49 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationChannelGroup, "android/app/NotificationChannelGroup", 26)
50 #undef JNI_CLASS_MEMBERS
51 
52 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
53   FIELD (extras, "extras", "Landroid/os/Bundle;")
54 
55 DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidNotification, "android/app/Notification", 19)
56 #undef JNI_CLASS_MEMBERS
57 
58 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
59   METHOD (addExtras,      "addExtras",      "(Landroid/os/Bundle;)Landroid/app/Notification$Action$Builder;") \
60   METHOD (addRemoteInput, "addRemoteInput", "(Landroid/app/RemoteInput;)Landroid/app/Notification$Action$Builder;") \
61   METHOD (constructor,    "<init>",         "(ILjava/lang/CharSequence;Landroid/app/PendingIntent;)V") \
62   METHOD (build,          "build",          "()Landroid/app/Notification$Action;")
63 
64 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationActionBuilder, "android/app/Notification$Action$Builder", 20)
65 #undef JNI_CLASS_MEMBERS
66 
67 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
68   METHOD (getNotification,  "getNotification",  "()Landroid/app/Notification;") \
69   METHOD (setAutoCancel,    "setAutoCancel",    "(Z)Landroid/app/Notification$Builder;") \
70   METHOD (setContentInfo,   "setContentInfo",   "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \
71   METHOD (setContentIntent, "setContentIntent", "(Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \
72   METHOD (setContentText,   "setContentText",   "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \
73   METHOD (setContentTitle,  "setContentTitle",  "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \
74   METHOD (setDefaults,      "setDefaults",      "(I)Landroid/app/Notification$Builder;") \
75   METHOD (setDeleteIntent,  "setDeleteIntent",  "(Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \
76   METHOD (setLargeIcon,     "setLargeIcon",     "(Landroid/graphics/Bitmap;)Landroid/app/Notification$Builder;") \
77   METHOD (setLights,        "setLights",        "(III)Landroid/app/Notification$Builder;") \
78   METHOD (setNumber,        "setNumber",        "(I)Landroid/app/Notification$Builder;") \
79   METHOD (setOngoing,       "setOngoing",       "(Z)Landroid/app/Notification$Builder;") \
80   METHOD (setOnlyAlertOnce, "setOnlyAlertOnce", "(Z)Landroid/app/Notification$Builder;") \
81   METHOD (setProgress,      "setProgress",      "(IIZ)Landroid/app/Notification$Builder;") \
82   METHOD (setSmallIcon,     "setSmallIcon",     "(I)Landroid/app/Notification$Builder;") \
83   METHOD (setSound,         "setSound",         "(Landroid/net/Uri;)Landroid/app/Notification$Builder;") \
84   METHOD (setTicker,        "setTicker",        "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \
85   METHOD (setVibrate,       "setVibrate",       "([J)Landroid/app/Notification$Builder;") \
86   METHOD (setWhen,          "setWhen",          "(J)Landroid/app/Notification$Builder;")
87 
88 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderBase, "android/app/Notification$Builder", 11)
89 #undef JNI_CLASS_MEMBERS
90 
91 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
92    METHOD (addAction,          "addAction",          "(ILjava/lang/CharSequence;Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \
93    METHOD (build,              "build",              "()Landroid/app/Notification;") \
94    METHOD (setPriority,        "setPriority",        "(I)Landroid/app/Notification$Builder;") \
95    METHOD (setSubText,         "setSubText",         "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \
96    METHOD (setUsesChronometer, "setUsesChronometer", "(Z)Landroid/app/Notification$Builder;")
97 
98 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi16, "android/app/Notification$Builder", 16)
99 #undef JNI_CLASS_MEMBERS
100 
101 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
102     METHOD (setShowWhen, "setShowWhen", "(Z)Landroid/app/Notification$Builder;")
103 
104 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi17, "android/app/Notification$Builder", 17)
105 #undef JNI_CLASS_MEMBERS
106 
107 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
108   METHOD (addAction,       "addAction",       "(Landroid/app/Notification$Action;)Landroid/app/Notification$Builder;") \
109   METHOD (addExtras,       "addExtras",       "(Landroid/os/Bundle;)Landroid/app/Notification$Builder;") \
110   METHOD (setLocalOnly,    "setLocalOnly",    "(Z)Landroid/app/Notification$Builder;") \
111   METHOD (setGroup,        "setGroup",        "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \
112   METHOD (setGroupSummary, "setGroupSummary", "(Z)Landroid/app/Notification$Builder;") \
113   METHOD (setSortKey,      "setSortKey",      "(Ljava/lang/String;)Landroid/app/Notification$Builder;")
114 
115 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi20, "android/app/Notification$Builder", 20)
116 #undef JNI_CLASS_MEMBERS
117 
118 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
119   METHOD (addPerson,        "addPerson",        "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \
120   METHOD (setCategory,      "setCategory",      "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \
121   METHOD (setColor,         "setColor",         "(I)Landroid/app/Notification$Builder;") \
122   METHOD (setPublicVersion, "setPublicVersion", "(Landroid/app/Notification;)Landroid/app/Notification$Builder;") \
123   METHOD (setVisibility,    "setVisibility",    "(I)Landroid/app/Notification$Builder;")
124 
125 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi21, "android/app/Notification$Builder", 21)
126 #undef JNI_CLASS_MEMBERS
127 
128 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
129   METHOD (setChronometerCountDown, "setChronometerCountDown", "(Z)Landroid/app/Notification$Builder;")
130 
131 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi24, "android/app/Notification$Builder", 24)
132 #undef JNI_CLASS_MEMBERS
133 
134 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
135   METHOD (setBadgeIconType,      "setBadgeIconType",      "(I)Landroid/app/Notification$Builder;") \
136   METHOD (setGroupAlertBehavior, "setGroupAlertBehavior", "(I)Landroid/app/Notification$Builder;") \
137   METHOD (setTimeoutAfter,       "setTimeoutAfter",       "(J)Landroid/app/Notification$Builder;")
138 
139 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi26, "android/app/Notification$Builder", 26)
140 #undef JNI_CLASS_MEMBERS
141 
142 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
143   METHOD (cancel,    "cancel",    "(Ljava/lang/String;I)V") \
144   METHOD (cancelAll, "cancelAll", "()V") \
145   METHOD (notify,    "notify",    "(Ljava/lang/String;ILandroid/app/Notification;)V")
146 
147 DECLARE_JNI_CLASS (NotificationManagerBase, "android/app/NotificationManager")
148 #undef JNI_CLASS_MEMBERS
149 
150 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
151   METHOD (getActiveNotifications, "getActiveNotifications", "()[Landroid/service/notification/StatusBarNotification;")
152 
153 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi23, "android/app/NotificationManager", 23)
154 #undef JNI_CLASS_MEMBERS
155 
156 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
157   METHOD (areNotificationsEnabled, "areNotificationsEnabled", "()Z")
158 
159 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi24, "android/app/NotificationManager", 24)
160 #undef JNI_CLASS_MEMBERS
161 
162 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
163   METHOD (createNotificationChannel,      "createNotificationChannel",      "(Landroid/app/NotificationChannel;)V") \
164   METHOD (createNotificationChannelGroup, "createNotificationChannelGroup", "(Landroid/app/NotificationChannelGroup;)V")
165 
166 DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi26, "android/app/NotificationManager", 26)
167 #undef JNI_CLASS_MEMBERS
168 
169 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
170   STATICMETHOD (getResultsFromIntent, "getResultsFromIntent", "(Landroid/content/Intent;)Landroid/os/Bundle;")
171 
172 DECLARE_JNI_CLASS_WITH_MIN_SDK (RemoteInput, "android/app/RemoteInput", 20)
173 #undef JNI_CLASS_MEMBERS
174 
175 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
176   METHOD (constructor,           "<init>",                "(Ljava/lang/String;)V") \
177   METHOD (build,                 "build",                 "()Landroid/app/RemoteInput;") \
178   METHOD (setAllowFreeFormInput, "setAllowFreeFormInput", "(Z)Landroid/app/RemoteInput$Builder;") \
179   METHOD (setChoices,            "setChoices",            "([Ljava/lang/CharSequence;)Landroid/app/RemoteInput$Builder;") \
180   METHOD (setLabel,              "setLabel",              "(Ljava/lang/CharSequence;)Landroid/app/RemoteInput$Builder;")
181 
182 DECLARE_JNI_CLASS_WITH_MIN_SDK (RemoteInputBuilder, "android/app/RemoteInput$Builder", 20)
183 #undef JNI_CLASS_MEMBERS
184 
185 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
186   METHOD (getNotification, "getNotification", "()Landroid/app/Notification;")
187 
188  DECLARE_JNI_CLASS_WITH_MIN_SDK (StatusBarNotification, "android/service/notification/StatusBarNotification", 23)
189  #undef JNI_CLASS_MEMBERS
190 
191 //==============================================================================
192 #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME)
193  #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
194    STATICMETHOD (getInstance, "getInstance", "()Lcom/google/firebase/iid/FirebaseInstanceId;") \
195    METHOD (getToken, "getToken", "()Ljava/lang/String;")
196 
197  DECLARE_JNI_CLASS (FirebaseInstanceId, "com/google/firebase/iid/FirebaseInstanceId")
198  #undef JNI_CLASS_MEMBERS
199 #endif
200 
201 #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
202  #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
203    STATICMETHOD (getInstance, "getInstance", "()Lcom/google/firebase/messaging/FirebaseMessaging;") \
204    METHOD (send,                 "send",                 "(Lcom/google/firebase/messaging/RemoteMessage;)V") \
205    METHOD (subscribeToTopic,     "subscribeToTopic",     "(Ljava/lang/String;)Lcom/google/android/gms/tasks/Task;") \
206    METHOD (unsubscribeFromTopic, "unsubscribeFromTopic", "(Ljava/lang/String;)Lcom/google/android/gms/tasks/Task;") \
207 
208  DECLARE_JNI_CLASS (FirebaseMessaging, "com/google/firebase/messaging/FirebaseMessaging")
209  #undef JNI_CLASS_MEMBERS
210 
211  #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
212    METHOD (getCollapseKey,  "getCollapseKey",  "()Ljava/lang/String;") \
213    METHOD (getData,         "getData",         "()Ljava/util/Map;") \
214    METHOD (getFrom,         "getFrom",         "()Ljava/lang/String;") \
215    METHOD (getMessageId,    "getMessageId",    "()Ljava/lang/String;") \
216    METHOD (getMessageType,  "getMessageType",  "()Ljava/lang/String;") \
217    METHOD (getNotification, "getNotification", "()Lcom/google/firebase/messaging/RemoteMessage$Notification;") \
218    METHOD (getSentTime,     "getSentTime",     "()J") \
219    METHOD (getTo,           "getTo",           "()Ljava/lang/String;") \
220    METHOD (getTtl,          "getTtl",          "()I")
221 
222  DECLARE_JNI_CLASS (RemoteMessage, "com/google/firebase/messaging/RemoteMessage")
223  #undef JNI_CLASS_MEMBERS
224 
225   #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
226    METHOD (addData,        "addData",        "(Ljava/lang/String;Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \
227    METHOD (build,          "build",          "()Lcom/google/firebase/messaging/RemoteMessage;") \
228    METHOD (constructor,    "<init>",         "(Ljava/lang/String;)V") \
229    METHOD (setCollapseKey, "setCollapseKey", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \
230    METHOD (setMessageId,   "setMessageId",   "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \
231    METHOD (setMessageType, "setMessageType", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \
232    METHOD (setTtl,         "setTtl",         "(I)Lcom/google/firebase/messaging/RemoteMessage$Builder;")
233 
234  DECLARE_JNI_CLASS (RemoteMessageBuilder, "com/google/firebase/messaging/RemoteMessage$Builder")
235  #undef JNI_CLASS_MEMBERS
236 
237  #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
238    METHOD (getBody,                  "getBody",                  "()Ljava/lang/String;") \
239    METHOD (getBodyLocalizationArgs,  "getBodyLocalizationArgs",  "()[Ljava/lang/String;") \
240    METHOD (getBodyLocalizationKey,   "getBodyLocalizationKey",   "()Ljava/lang/String;") \
241    METHOD (getClickAction,           "getClickAction",           "()Ljava/lang/String;") \
242    METHOD (getColor,                 "getColor",                 "()Ljava/lang/String;") \
243    METHOD (getIcon,                  "getIcon",                  "()Ljava/lang/String;") \
244    METHOD (getLink,                  "getLink",                  "()Landroid/net/Uri;") \
245    METHOD (getSound,                 "getSound",                 "()Ljava/lang/String;") \
246    METHOD (getTag,                   "getTag",                   "()Ljava/lang/String;") \
247    METHOD (getTitle,                 "getTitle",                 "()Ljava/lang/String;") \
248    METHOD (getTitleLocalizationArgs, "getTitleLocalizationArgs", "()[Ljava/lang/String;") \
249    METHOD (getTitleLocalizationKey,  "getTitleLocalizationKey",  "()Ljava/lang/String;")
250 
251  DECLARE_JNI_CLASS (RemoteMessageNotification, "com/google/firebase/messaging/RemoteMessage$Notification")
252  #undef JNI_CLASS_MEMBERS
253 #endif
254 
255 //==============================================================================
isValid() const256 bool PushNotifications::Notification::isValid() const noexcept
257 {
258     bool isValidForPreApi26 = title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && icon.isNotEmpty();
259     bool apiAtLeast26 = (getAndroidSDKVersion() >= 26);
260 
261     if (apiAtLeast26)
262         return isValidForPreApi26 && channelId.isNotEmpty();
263 
264     return isValidForPreApi26;
265 }
266 
267 //==============================================================================
268 struct PushNotifications::Pimpl
269 {
Pimpljuce::PushNotifications::Pimpl270     Pimpl (PushNotifications& p)
271         : owner (p)
272     {}
273 
areNotificationsEnabledjuce::PushNotifications::Pimpl274     bool areNotificationsEnabled() const
275     {
276         if (getAndroidSDKVersion() >= 24)
277         {
278             auto* env = getEnv();
279 
280             auto notificationManager = getNotificationManager();
281 
282             if (notificationManager.get() != nullptr)
283                 return env->CallBooleanMethod (notificationManager, NotificationManagerApi24.areNotificationsEnabled);
284         }
285 
286         return true;
287     }
288 
289     //==============================================================================
sendLocalNotificationjuce::PushNotifications::Pimpl290     void sendLocalNotification (const PushNotifications::Notification& n)
291     {
292         // All required fields have to be setup!
293         jassert (n.isValid());
294 
295         auto* env = getEnv();
296 
297         auto notificationManager = getNotificationManager();
298 
299         if (notificationManager.get() != nullptr)
300         {
301             auto notification = juceNotificationToJavaNotification (n);
302 
303             auto tag = javaString (n.identifier);
304             const int id = 0;
305 
306             env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.notify, tag.get(), id, notification.get());
307         }
308     }
309 
getDeliveredNotificationsjuce::PushNotifications::Pimpl310     void getDeliveredNotifications() const
311     {
312         if (getAndroidSDKVersion() >= 23)
313         {
314             auto* env = getEnv();
315 
316             Array<PushNotifications::Notification> notifications;
317 
318             auto notificationManager = getNotificationManager();
319             jassert (notificationManager != nullptr);
320 
321             if (notificationManager.get() != nullptr)
322             {
323                 auto statusBarNotifications = LocalRef<jobjectArray> ((jobjectArray)env->CallObjectMethod (notificationManager,
324                                                                                                            NotificationManagerApi23.getActiveNotifications));
325 
326                 const int numNotifications = env->GetArrayLength (statusBarNotifications.get());
327 
328                 for (int i = 0; i < numNotifications; ++i)
329                 {
330                     auto statusBarNotification = LocalRef<jobject> (env->GetObjectArrayElement (statusBarNotifications.get(), (jsize) i));
331                     auto notification = LocalRef<jobject> (env->CallObjectMethod (statusBarNotification, StatusBarNotification.getNotification));
332 
333                     notifications.add (javaNotificationToJuceNotification (notification));
334                 }
335             }
336 
337             owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifications); });
338         }
339         else
340         {
341             // Not supported on this platform
342             jassertfalse;
343             owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); });
344         }
345     }
346 
notifyListenersAboutLocalNotificationjuce::PushNotifications::Pimpl347     void notifyListenersAboutLocalNotification (const LocalRef<jobject>& intent)
348     {
349         auto* env = getEnv();
350         LocalRef<jobject> context (getMainActivity());
351 
352         auto bundle = LocalRef<jobject> (env->CallObjectMethod (intent, AndroidIntent.getExtras));
353 
354         const auto notification = localNotificationBundleToJuceNotification (bundle);
355 
356         auto packageName  = juceString ((jstring) env->CallObjectMethod (context.get(), AndroidContext.getPackageName));
357 
358         String notificationString                = packageName + ".JUCE_NOTIFICATION.";
359         String notificationButtonActionString    = packageName + ".JUCE_NOTIFICATION_BUTTON_ACTION.";
360         String notificationTextInputActionString = packageName + ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION.";
361 
362         auto actionString = juceString ((jstring) env->CallObjectMethod (intent, AndroidIntent.getAction));
363 
364         if (actionString.contains (notificationString))
365         {
366             owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, notification); });
367         }
368         else if (actionString.contains (notificationButtonActionString))
369         {
370             auto prefix = notificationButtonActionString + notification.identifier + ".";
371 
372             auto actionTitle = actionString.fromLastOccurrenceOf (prefix, false, false)     // skip prefix
373                                            .fromFirstOccurrenceOf (".", false, false);      // skip action index
374 
375             owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, notification, actionTitle, {}); });
376         }
377         else if (getAndroidSDKVersion() >= 20 && actionString.contains (notificationTextInputActionString))
378         {
379             auto prefix = notificationTextInputActionString + notification.identifier + ".";
380 
381             auto actionTitle = actionString.fromLastOccurrenceOf (prefix, false, false)     // skip prefix
382                                            .fromFirstOccurrenceOf (".", false, false);      // skip action index
383 
384             auto actionIndex = actionString.fromLastOccurrenceOf (prefix, false, false).upToFirstOccurrenceOf (".", false, false);
385             auto resultKeyString = javaString (actionTitle + actionIndex);
386 
387             auto remoteInputResult = LocalRef<jobject> (env->CallStaticObjectMethod (RemoteInput, RemoteInput.getResultsFromIntent, intent.get()));
388             String responseString;
389 
390             if (remoteInputResult.get() == nullptr)
391             {
392                 auto charSequence      = LocalRef<jobject> (env->CallObjectMethod (remoteInputResult, AndroidBundle.getCharSequence, resultKeyString.get()));
393                 auto responseStringRef = LocalRef<jstring> ((jstring) env->CallObjectMethod (charSequence, JavaCharSequence.toString));
394                 responseString = juceString (responseStringRef.get());
395             }
396 
397             owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, notification, actionTitle, responseString); });
398         }
399     }
400 
notifyListenersAboutLocalNotificationDeletedjuce::PushNotifications::Pimpl401     void notifyListenersAboutLocalNotificationDeleted (const LocalRef<jobject>& intent)
402     {
403         auto* env = getEnv();
404 
405         auto bundle = LocalRef<jobject> (env->CallObjectMethod (intent, AndroidIntent.getExtras));
406         auto notification = localNotificationBundleToJuceNotification (bundle);
407 
408         owner.listeners.call ([&] (Listener& l) { l.localNotificationDismissedByUser (notification); });
409     }
410 
removeAllDeliveredNotificationsjuce::PushNotifications::Pimpl411     void removeAllDeliveredNotifications()
412     {
413         auto* env = getEnv();
414 
415         auto notificationManager = getNotificationManager();
416 
417         if (notificationManager.get() != nullptr)
418             env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.cancelAll);
419     }
420 
removeDeliveredNotificationjuce::PushNotifications::Pimpl421     void removeDeliveredNotification (const String& identifier)
422     {
423         auto* env = getEnv();
424 
425         auto notificationManager = getNotificationManager();
426 
427         if (notificationManager.get() != nullptr)
428         {
429             auto tag = javaString (identifier);
430             const int id = 0;
431 
432             env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.cancel, tag.get(), id);
433         }
434     }
435 
436     //==============================================================================
getDeviceTokenjuce::PushNotifications::Pimpl437     String getDeviceToken() const
438     {
439       #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME)
440         auto* env = getEnv();
441 
442         auto instanceId = LocalRef<jobject> (env->CallStaticObjectMethod (FirebaseInstanceId, FirebaseInstanceId.getInstance));
443 
444         return juceString ((jstring) env->CallObjectMethod (instanceId, FirebaseInstanceId.getToken));
445       #else
446         return {};
447       #endif
448     }
449 
notifyListenersTokenRefreshedjuce::PushNotifications::Pimpl450     void notifyListenersTokenRefreshed (const String& token)
451     {
452       #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME)
453         MessageManager::callAsync ([this, token]
454         {
455             owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (token); });
456         });
457       #else
458         ignoreUnused (token);
459       #endif
460     }
461 
462     //==============================================================================
subscribeToTopicjuce::PushNotifications::Pimpl463     void subscribeToTopic (const String& topic)
464     {
465       #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
466         auto* env = getEnv();
467 
468         auto firebaseMessaging = LocalRef<jobject> (env->CallStaticObjectMethod (FirebaseMessaging,
469                                                                                  FirebaseMessaging.getInstance));
470 
471         env->CallObjectMethod (firebaseMessaging, FirebaseMessaging.subscribeToTopic, javaString (topic).get());
472       #else
473         ignoreUnused (topic);
474       #endif
475     }
476 
unsubscribeFromTopicjuce::PushNotifications::Pimpl477     void unsubscribeFromTopic (const String& topic)
478     {
479       #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
480         auto* env = getEnv();
481 
482         auto firebaseMessaging = LocalRef<jobject> (env->CallStaticObjectMethod (FirebaseMessaging,
483                                                                                  FirebaseMessaging.getInstance));
484 
485         env->CallObjectMethod (firebaseMessaging, FirebaseMessaging.unsubscribeFromTopic, javaString (topic).get());
486       #else
487         ignoreUnused (topic);
488       #endif
489     }
490 
sendUpstreamMessagejuce::PushNotifications::Pimpl491     void sendUpstreamMessage (const String& serverSenderId,
492                               const String& collapseKey,
493                               const String& messageId,
494                               const String& messageType,
495                               int timeToLive,
496                               const StringPairArray& additionalData)
497     {
498       #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
499         auto* env = getEnv();
500 
501         auto messageBuilder = LocalRef<jobject> (env->NewObject (RemoteMessageBuilder,
502                                                                  RemoteMessageBuilder.constructor,
503                                                                  javaString (serverSenderId + "@gcm_googleapis.com").get()));
504 
505         env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setCollapseKey, javaString (collapseKey).get());
506         env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setMessageId, javaString (messageId).get());
507         env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setMessageType, javaString (messageType).get());
508         env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setTtl, timeToLive);
509 
510         auto keys = additionalData.getAllKeys();
511 
512         for (const auto& key : keys)
513             env->CallObjectMethod (messageBuilder,
514                                    RemoteMessageBuilder.addData,
515                                    javaString (key).get(),
516                                    javaString (additionalData[key]).get());
517 
518         auto message = LocalRef<jobject> (env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.build));
519 
520         auto firebaseMessaging = LocalRef<jobject> (env->CallStaticObjectMethod (FirebaseMessaging,
521                                                                                  FirebaseMessaging.getInstance));
522 
523         env->CallVoidMethod (firebaseMessaging, FirebaseMessaging.send, message.get());
524       #else
525         ignoreUnused (serverSenderId, collapseKey, messageId, messageType);
526         ignoreUnused (timeToLive, additionalData);
527       #endif
528     }
529 
notifyListenersAboutRemoteNotificationFromSystemTrayjuce::PushNotifications::Pimpl530     void notifyListenersAboutRemoteNotificationFromSystemTray (const LocalRef<jobject>& intent)
531     {
532       #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
533         auto* env = getEnv();
534 
535         auto bundle = LocalRef<jobject> (env->CallObjectMethod (intent, AndroidIntent.getExtras));
536         auto notification = remoteNotificationBundleToJuceNotification (bundle);
537 
538         owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, notification); });
539       #else
540         ignoreUnused (intent);
541       #endif
542     }
543 
notifyListenersAboutRemoteNotificationFromServicejuce::PushNotifications::Pimpl544     void notifyListenersAboutRemoteNotificationFromService (const LocalRef<jobject>& remoteNotification)
545     {
546       #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
547         GlobalRef rn (remoteNotification);
548 
549         MessageManager::callAsync ([this, rn]
550         {
551             auto notification = firebaseRemoteNotificationToJuceNotification (rn.get());
552             owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, notification); });
553         });
554       #else
555         ignoreUnused (remoteNotification);
556       #endif
557     }
558 
notifyListenersAboutRemoteNotificationsDeletedjuce::PushNotifications::Pimpl559     void notifyListenersAboutRemoteNotificationsDeleted()
560     {
561       #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
562         MessageManager::callAsync ([this]
563         {
564             owner.listeners.call ([] (Listener& l) { l.remoteNotificationsDeleted(); });
565         });
566       #endif
567     }
568 
notifyListenersAboutUpstreamMessageSentjuce::PushNotifications::Pimpl569     void notifyListenersAboutUpstreamMessageSent (const LocalRef<jstring>& messageId)
570     {
571       #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
572         GlobalRef mid (LocalRef<jobject>(messageId.get()));
573 
574         MessageManager::callAsync ([this, mid]
575         {
576             auto midString = juceString ((jstring) mid.get());
577             owner.listeners.call ([&] (Listener& l) { l.upstreamMessageSent (midString); });
578         });
579       #else
580         ignoreUnused (messageId);
581       #endif
582     }
583 
notifyListenersAboutUpstreamMessageSendingErrorjuce::PushNotifications::Pimpl584     void notifyListenersAboutUpstreamMessageSendingError (const LocalRef<jstring>& messageId,
585                                                           const LocalRef<jstring>& error)
586     {
587       #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
588         GlobalRef mid (LocalRef<jobject>(messageId.get())), e (LocalRef<jobject>(error.get()));
589 
590         MessageManager::callAsync ([this, mid, e]
591         {
592             auto midString = juceString ((jstring) mid.get());
593             auto eString   = juceString ((jstring) e.get());
594 
595             owner.listeners.call ([&] (Listener& l) { l.upstreamMessageSendingError (midString, eString); });
596         });
597       #else
598         ignoreUnused (messageId, error);
599       #endif
600     }
601 
getNotificationManagerjuce::PushNotifications::Pimpl602     static LocalRef<jobject> getNotificationManager()
603     {
604         auto* env = getEnv();
605         LocalRef<jobject> context (getMainActivity());
606 
607         return LocalRef<jobject> (env->CallObjectMethod (context.get(),
608                                                          AndroidContext.getSystemService,
609                                                          javaString ("notification").get()));
610     }
611 
juceNotificationToJavaNotificationjuce::PushNotifications::Pimpl612     static LocalRef<jobject> juceNotificationToJavaNotification (const PushNotifications::Notification& n)
613     {
614         auto* env = getEnv();
615 
616         auto notificationBuilder = createNotificationBuilder (n);
617 
618         setupRequiredFields (n, notificationBuilder);
619         setupOptionalFields (n, notificationBuilder);
620 
621         if (n.actions.size() > 0)
622             setupActions (n, notificationBuilder);
623 
624         if (getAndroidSDKVersion() >= 16)
625             return LocalRef<jobject> (env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.build));
626 
627         return LocalRef<jobject> (env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.getNotification));
628     }
629 
createNotificationBuilderjuce::PushNotifications::Pimpl630     static LocalRef<jobject> createNotificationBuilder (const PushNotifications::Notification& n)
631     {
632         auto* env = getEnv();
633         LocalRef<jobject> context (getMainActivity());
634 
635         jclass builderClass = env->FindClass ("android/app/Notification$Builder");
636         jassert (builderClass != nullptr);
637 
638         if (builderClass == nullptr)
639             return LocalRef<jobject> (nullptr);
640 
641         jmethodID builderConstructor = nullptr;
642 
643         const bool apiAtLeast26 = (getAndroidSDKVersion() >= 26);
644 
645         if (apiAtLeast26)
646             builderConstructor = env->GetMethodID (builderClass, "<init>", "(Landroid/content/Context;Ljava/lang/String;)V");
647         else
648             builderConstructor = env->GetMethodID (builderClass, "<init>", "(Landroid/content/Context;)V");
649 
650         jassert (builderConstructor != nullptr);
651 
652         if (builderConstructor == nullptr)
653             return LocalRef<jobject> (nullptr);
654 
655         if (apiAtLeast26)
656             return LocalRef<jobject> (env->NewObject (builderClass, builderConstructor,
657                                                       context.get(), javaString (n.channelId).get()));
658 
659         return LocalRef<jobject> (env->NewObject (builderClass, builderConstructor, context.get()));
660     }
661 
setupRequiredFieldsjuce::PushNotifications::Pimpl662     static void setupRequiredFields (const PushNotifications::Notification& n, LocalRef<jobject>& notificationBuilder)
663     {
664         auto* env = getEnv();
665         LocalRef<jobject> context (getMainActivity());
666 
667         auto activityClass = LocalRef<jobject> (env->CallObjectMethod (context.get(), JavaObject.getClass));
668         auto notifyIntent  = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get()));
669 
670         auto packageNameString  = LocalRef<jstring> ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName)));
671         auto actionStringSuffix = javaString (".JUCE_NOTIFICATION." + n.identifier);
672         auto actionString       = LocalRef<jstring> ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get()));
673 
674         env->CallObjectMethod (notifyIntent, AndroidIntent.setAction, actionString.get());
675         // Packaging entire notification into extras bundle here, so that we can retrieve all the details later on
676         env->CallObjectMethod (notifyIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get());
677 
678         auto notifyPendingIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidPendingIntent,
679                                                                                    AndroidPendingIntent.getActivity,
680                                                                                    context.get(),
681                                                                                    1002,
682                                                                                    notifyIntent.get(),
683                                                                                    0));
684 
685         env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentTitle,  javaString (n.title).get());
686         env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentText,   javaString (n.body).get());
687         env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentIntent, notifyPendingIntent.get());
688 
689         auto resources = LocalRef<jobject> (env->CallObjectMethod (context.get(), AndroidContext.getResources));
690         const int iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(),
691                                                javaString ("raw").get(), packageNameString.get());
692 
693         env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setSmallIcon, iconId);
694 
695         if (getAndroidSDKVersion() >= 21 && n.publicVersion != nullptr)
696         {
697             // Public version of a notification is not expected to have another public one!
698             jassert (n.publicVersion->publicVersion == nullptr);
699 
700             auto publicNotificationBuilder = createNotificationBuilder (n);
701 
702             setupRequiredFields (*n.publicVersion, publicNotificationBuilder);
703             setupOptionalFields (*n.publicVersion, publicNotificationBuilder);
704 
705             auto publicVersion = LocalRef<jobject> (env->CallObjectMethod (publicNotificationBuilder, NotificationBuilderApi16.build));
706             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setPublicVersion, publicVersion.get());
707         }
708     }
709 
juceNotificationToBundlejuce::PushNotifications::Pimpl710     static LocalRef<jobject> juceNotificationToBundle (const PushNotifications::Notification& n)
711     {
712         auto* env = getEnv();
713 
714         auto bundle = LocalRef<jobject> (env->NewObject (AndroidBundle, AndroidBundle.constructor));
715 
716         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("identifier")              .get(), javaString (n.identifier).get());
717         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("title")                   .get(), javaString (n.title).get());
718         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("body")                    .get(), javaString (n.body).get());
719         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("subtitle")                .get(), javaString (n.subtitle).get());
720         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("badgeNumber")             .get(), n.badgeNumber);
721         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("soundToPlay")             .get(), javaString (n.soundToPlay.toString (true)).get());
722         env->CallVoidMethod (bundle, AndroidBundle.putBundle,   javaString ("properties")              .get(), varToBundleWithPropertiesString (n.properties).get());
723         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("icon")                    .get(), javaString (n.icon).get());
724         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("channelId")               .get(), javaString (n.channelId).get());
725         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("tickerText")              .get(), javaString (n.tickerText).get());
726         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("progressMax")             .get(), n.progress.max);
727         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("progressCurrent")         .get(), n.progress.current);
728         env->CallVoidMethod (bundle, AndroidBundle.putBoolean,  javaString ("progressIndeterminate")   .get(), n.progress.indeterminate);
729         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("person")                  .get(), javaString (n.person).get());
730         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("type")                    .get(), n.type);
731         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("priority")                .get(), n.priority);
732         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("lockScreenAppearance")    .get(), n.lockScreenAppearance);
733         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("groupId")                 .get(), javaString (n.groupId).get());
734         env->CallVoidMethod (bundle, AndroidBundle.putString,   javaString ("groupSortKey")            .get(), javaString (n.groupSortKey).get());
735         env->CallVoidMethod (bundle, AndroidBundle.putBoolean,  javaString ("groupSummary")            .get(), n.groupSummary);
736         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("accentColour")            .get(), n.accentColour.getARGB());
737         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("ledColour")               .get(), n.ledColour.getARGB());
738         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("ledBlinkPatternMsToBeOn") .get(), n.ledBlinkPattern.msToBeOn);
739         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("ledBlinkPatternMsToBeOff").get(), n.ledBlinkPattern.msToBeOff);
740         env->CallVoidMethod (bundle, AndroidBundle.putBoolean,  javaString ("shouldAutoCancel")        .get(), n.shouldAutoCancel);
741         env->CallVoidMethod (bundle, AndroidBundle.putBoolean,  javaString ("localOnly")               .get(), n.localOnly);
742         env->CallVoidMethod (bundle, AndroidBundle.putBoolean,  javaString ("ongoing")                 .get(), n.ongoing);
743         env->CallVoidMethod (bundle, AndroidBundle.putBoolean,  javaString ("alertOnlyOnce")           .get(), n.alertOnlyOnce);
744         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("timestampVisibility")     .get(), n.timestampVisibility);
745         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("badgeIconType")           .get(), n.badgeIconType);
746         env->CallVoidMethod (bundle, AndroidBundle.putInt,      javaString ("groupAlertBehaviour")     .get(), n.groupAlertBehaviour);
747         env->CallVoidMethod (bundle, AndroidBundle.putLong,     javaString ("timeoutAfterMs")          .get(), (jlong)n.timeoutAfterMs);
748 
749         const int size = n.vibrationPattern.size();
750 
751         if (size > 0)
752         {
753             auto array = LocalRef<jlongArray> (env->NewLongArray (size));
754 
755             jlong* elements = env->GetLongArrayElements (array, nullptr);
756 
757             for (int i = 0; i < size; ++i)
758                 elements[i] = (jlong) n.vibrationPattern[i];
759 
760             env->SetLongArrayRegion (array, 0, size, elements);
761             env->CallVoidMethod (bundle, AndroidBundle.putLongArray, javaString ("vibrationPattern").get(), array.get());
762         }
763 
764         return bundle;
765     }
766 
setupOptionalFieldsjuce::PushNotifications::Pimpl767     static void setupOptionalFields (const PushNotifications::Notification& n, LocalRef<jobject>& notificationBuilder)
768     {
769         auto* env = getEnv();
770 
771         if (n.subtitle.isNotEmpty())
772             env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentInfo, javaString (n.subtitle).get());
773 
774         auto soundName = n.soundToPlay.toString (true);
775 
776         if (soundName == "default_os_sound")
777         {
778             const int playDefaultSound = 1;
779             env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDefaults, playDefaultSound);
780         }
781         else if (! soundName.isEmpty())
782         {
783             env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setSound, juceUrlToAndroidUri (n.soundToPlay).get());
784         }
785 
786         if (n.largeIcon.isValid())
787             env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setLargeIcon, imagetoJavaBitmap (n.largeIcon).get());
788 
789         if (n.tickerText.isNotEmpty())
790             env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setTicker, javaString (n.tickerText).get());
791 
792         if (n.ledColour != Colour())
793         {
794             env->CallObjectMethod (notificationBuilder,
795                                    NotificationBuilderBase.setLights,
796                                    n.ledColour.getARGB(),
797                                    n.ledBlinkPattern.msToBeOn,
798                                    n.ledBlinkPattern.msToBeOff);
799         }
800 
801         if (! n.vibrationPattern.isEmpty())
802         {
803             const int size = n.vibrationPattern.size();
804 
805             if (size > 0)
806             {
807                 auto array = LocalRef<jlongArray> (env->NewLongArray (size));
808 
809                 jlong* elements = env->GetLongArrayElements (array, nullptr);
810 
811                 for (int i = 0; i < size; ++i)
812                     elements[i] = (jlong) n.vibrationPattern[i];
813 
814                 env->SetLongArrayRegion (array, 0, size, elements);
815                 env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setVibrate, array.get());
816             }
817         }
818 
819         env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setProgress, n.progress.max, n.progress.current, n.progress.indeterminate);
820         env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setNumber, n.badgeNumber);
821         env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setAutoCancel, n.shouldAutoCancel);
822         env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setOngoing, n.ongoing);
823         env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setOnlyAlertOnce, n.alertOnlyOnce);
824 
825         if (getAndroidSDKVersion() >= 16)
826         {
827             if (n.subtitle.isNotEmpty())
828                 env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setSubText, javaString (n.subtitle).get());
829 
830             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setPriority, n.priority);
831 
832             if (getAndroidSDKVersion() < 24)
833             {
834                 const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer;
835                 env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer);
836             }
837         }
838 
839         if (getAndroidSDKVersion() >= 17)
840         {
841             const bool showTimeStamp = n.timestampVisibility != PushNotifications::Notification::off;
842             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi17.setShowWhen, showTimeStamp);
843         }
844 
845         if (getAndroidSDKVersion() >= 20)
846         {
847             if (n.groupId.isNotEmpty())
848             {
849                 env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroup, javaString (n.groupId).get());
850                 env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroupSummary, n.groupSummary);
851             }
852 
853             if (n.groupSortKey.isNotEmpty())
854                 env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setSortKey, javaString (n.groupSortKey).get());
855 
856             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setLocalOnly, n.localOnly);
857 
858             auto extras = LocalRef<jobject> (env->NewObject (AndroidBundle, AndroidBundle.constructor));
859 
860             env->CallVoidMethod (extras, AndroidBundle.putBundle, javaString ("notificationData").get(),
861                                  juceNotificationToBundle (n).get());
862 
863             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addExtras, extras.get());
864         }
865 
866         if (getAndroidSDKVersion() >= 21)
867         {
868             if (n.person.isNotEmpty())
869                 env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.addPerson, javaString (n.person).get());
870 
871             auto categoryString = typeToCategory (n.type);
872             if (categoryString.isNotEmpty())
873                 env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setCategory, javaString (categoryString).get());
874 
875             if (n.accentColour != Colour())
876                 env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setColor, n.accentColour.getARGB());
877 
878             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setVisibility, n.lockScreenAppearance);
879         }
880 
881         if (getAndroidSDKVersion() >= 24)
882         {
883             const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer;
884             const bool useCountDownChronometer = n.timestampVisibility == PushNotifications::Notification::countDownChronometer;
885 
886             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi24.setChronometerCountDown, useCountDownChronometer);
887             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer | useCountDownChronometer);
888         }
889 
890         if (getAndroidSDKVersion() >= 26)
891         {
892             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setBadgeIconType, n.badgeIconType);
893             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setGroupAlertBehavior, n.groupAlertBehaviour);
894             env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setTimeoutAfter, (jlong) n.timeoutAfterMs);
895         }
896 
897         setupNotificationDeletedCallback (n, notificationBuilder);
898     }
899 
setupNotificationDeletedCallbackjuce::PushNotifications::Pimpl900     static void setupNotificationDeletedCallback (const PushNotifications::Notification& n,
901                                                   LocalRef<jobject>& notificationBuilder)
902     {
903         auto* env = getEnv();
904         LocalRef<jobject> context (getMainActivity());
905 
906         auto activityClass = LocalRef<jobject> (env->CallObjectMethod (context.get(), JavaObject.getClass));
907         auto deleteIntent  = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get()));
908 
909         auto packageNameString  = LocalRef<jstring> ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName)));
910         auto actionStringSuffix = javaString (".JUCE_NOTIFICATION_DELETED." + n.identifier);
911         auto actionString       = LocalRef<jstring> ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get()));
912 
913         env->CallObjectMethod (deleteIntent, AndroidIntent.setAction, actionString.get());
914         env->CallObjectMethod (deleteIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get());
915 
916         auto deletePendingIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidPendingIntent,
917                                                                                    AndroidPendingIntent.getActivity,
918                                                                                    context.get(),
919                                                                                    1002,
920                                                                                    deleteIntent.get(),
921                                                                                    0));
922 
923         env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDeleteIntent, deletePendingIntent.get());
924     }
925 
setupActionsjuce::PushNotifications::Pimpl926     static void setupActions (const PushNotifications::Notification& n, LocalRef<jobject>& notificationBuilder)
927     {
928         if (getAndroidSDKVersion() < 16)
929             return;
930 
931         auto* env = getEnv();
932         LocalRef<jobject> context (getMainActivity());
933 
934         int actionIndex = 0;
935 
936         for (const auto& action : n.actions)
937         {
938             auto activityClass = LocalRef<jobject> (env->CallObjectMethod (context.get(), JavaObject.getClass));
939             auto notifyIntent  = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get()));
940 
941             const bool isTextStyle = action.style == PushNotifications::Notification::Action::text;
942 
943             auto packageNameString   = LocalRef<jstring> ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName)));
944             const String notificationActionString = isTextStyle ? ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION." : ".JUCE_NOTIFICATION_BUTTON_ACTION.";
945             auto actionStringSuffix  = javaString (notificationActionString + n.identifier + "." + String (actionIndex) + "." + action.title);
946             auto actionString        = LocalRef<jstring> ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get()));
947 
948             env->CallObjectMethod (notifyIntent, AndroidIntent.setAction, actionString.get());
949             // Packaging entire notification into extras bundle here, so that we can retrieve all the details later on
950             env->CallObjectMethod (notifyIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get());
951 
952             auto notifyPendingIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidPendingIntent,
953                                                                                        AndroidPendingIntent.getActivity,
954                                                                                        context.get(),
955                                                                                        1002,
956                                                                                        notifyIntent.get(),
957                                                                                        0));
958 
959             auto resources = LocalRef<jobject> (env->CallObjectMethod (context.get(), AndroidContext.getResources));
960             int iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (action.icon).get(),
961                                              javaString ("raw").get(), packageNameString.get());
962 
963             if (iconId == 0)
964                 iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(),
965                                              javaString ("raw").get(), packageNameString.get());
966 
967             if (getAndroidSDKVersion() >= 20)
968             {
969                 auto actionBuilder = LocalRef<jobject> (env->NewObject (NotificationActionBuilder,
970                                                                         NotificationActionBuilder.constructor,
971                                                                         iconId,
972                                                                         javaString (action.title).get(),
973                                                                         notifyPendingIntent.get()));
974 
975                 env->CallObjectMethod (actionBuilder, NotificationActionBuilder.addExtras,
976                                        varToBundleWithPropertiesString (action.parameters).get());
977 
978                 if (isTextStyle)
979                 {
980                     auto resultKey = javaString (action.title + String (actionIndex));
981                     auto remoteInputBuilder = LocalRef<jobject> (env->NewObject (RemoteInputBuilder,
982                                                                                  RemoteInputBuilder.constructor,
983                                                                                  resultKey.get()));
984 
985                     if (! action.textInputPlaceholder.isEmpty())
986                         env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setLabel, javaString (action.textInputPlaceholder).get());
987 
988                     if (! action.allowedResponses.isEmpty())
989                     {
990                         env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setAllowFreeFormInput, false);
991 
992                         const int size = action.allowedResponses.size();
993 
994                         auto array = LocalRef<jobjectArray> (env->NewObjectArray (size, env->FindClass ("java/lang/String"), nullptr));
995 
996                         for (int i = 0; i < size; ++i)
997                         {
998                             const auto& response = action.allowedResponses[i];
999                             auto responseString = javaString (response);
1000 
1001                             env->SetObjectArrayElement (array, i, responseString.get());
1002                         }
1003 
1004                         env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setChoices, array.get());
1005                     }
1006 
1007                     env->CallObjectMethod (actionBuilder, NotificationActionBuilder.addRemoteInput,
1008                                            env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.build));
1009                 }
1010 
1011                 env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addAction,
1012                                        env->CallObjectMethod (actionBuilder, NotificationActionBuilder.build));
1013             }
1014             else
1015             {
1016                 env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.addAction,
1017                                        iconId, javaString (action.title).get(), notifyPendingIntent.get());
1018             }
1019 
1020             ++actionIndex;
1021         }
1022     }
1023 
juceUrlToAndroidUrijuce::PushNotifications::Pimpl1024     static LocalRef<jobject> juceUrlToAndroidUri (const URL& url)
1025     {
1026         auto* env = getEnv();
1027         LocalRef<jobject> context (getMainActivity());
1028 
1029         auto packageNameString = LocalRef<jstring> ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName)));
1030 
1031         auto resources = LocalRef<jobject> (env->CallObjectMethod (context.get(), AndroidContext.getResources));
1032         const int id = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (url.toString (true)).get(),
1033                                            javaString ("raw").get(), packageNameString.get());
1034 
1035         auto schemeString   = javaString ("android.resource://");
1036         auto resourceString = javaString ("/" + String (id));
1037         auto uriString = LocalRef<jstring> ((jstring) env->CallObjectMethod (schemeString, JavaString.concat, packageNameString.get()));
1038         uriString = LocalRef<jstring> ((jstring) env->CallObjectMethod (uriString, JavaString.concat, resourceString.get()));
1039 
1040         return LocalRef<jobject> (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, uriString.get()));
1041     }
1042 
imagetoJavaBitmapjuce::PushNotifications::Pimpl1043     static LocalRef<jobject> imagetoJavaBitmap (const Image& image)
1044     {
1045         auto* env = getEnv();
1046 
1047         Image imageToUse = image.convertedToFormat (Image::PixelFormat::ARGB);
1048 
1049         auto bitmapConfig = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidBitmapConfig,
1050                                                                             AndroidBitmapConfig.valueOf,
1051                                                                             javaString ("ARGB_8888").get()));
1052 
1053         auto bitmap = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidBitmap,
1054                                                                       AndroidBitmap.createBitmap,
1055                                                                       image.getWidth(),
1056                                                                       image.getHeight(),
1057                                                                       bitmapConfig.get()));
1058 
1059         for (int i = 0; i < image.getWidth(); ++i)
1060             for (int j = 0; j < image.getHeight(); ++j)
1061                 env->CallVoidMethod (bitmap.get(), AndroidBitmap.setPixel, i, j, image.getPixelAt (i, j).getARGB());
1062 
1063         return bitmap;
1064     }
1065 
typeToCategoryjuce::PushNotifications::Pimpl1066     static String typeToCategory (PushNotifications::Notification::Type t)
1067     {
1068         switch (t)
1069         {
1070             case PushNotifications::Notification::unspecified:    return {};
1071             case PushNotifications::Notification::alarm:          return "alarm";
1072             case PushNotifications::Notification::call:           return "call";
1073             case PushNotifications::Notification::email:          return "email";
1074             case PushNotifications::Notification::error:          return "err";
1075             case PushNotifications::Notification::event:          return "event";
1076             case PushNotifications::Notification::message:        return "msg";
1077             case PushNotifications::Notification::taskProgress:   return "progress";
1078             case PushNotifications::Notification::promo:          return "promo";
1079             case PushNotifications::Notification::recommendation: return "recommendation";
1080             case PushNotifications::Notification::reminder:       return "reminder";
1081             case PushNotifications::Notification::service:        return "service";
1082             case PushNotifications::Notification::social:         return "social";
1083             case PushNotifications::Notification::status:         return "status";
1084             case PushNotifications::Notification::system:         return "sys";
1085             case PushNotifications::Notification::transport:      return "transport";
1086         }
1087 
1088         return {};
1089     }
1090 
varToBundleWithPropertiesStringjuce::PushNotifications::Pimpl1091     static LocalRef<jobject> varToBundleWithPropertiesString (const var& varToParse)
1092     {
1093         auto* env = getEnv();
1094 
1095         auto bundle = LocalRef<jobject> (env->NewObject (AndroidBundle, AndroidBundle.constructor));
1096         env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("properties").get(),
1097                              javaString (JSON::toString (varToParse, false)).get());
1098 
1099         return bundle;
1100     }
1101 
1102     // Gets "properties" var from bundle.
bundleWithPropertiesStringToVarjuce::PushNotifications::Pimpl1103     static var bundleWithPropertiesStringToVar (const LocalRef<jobject>& bundle)
1104     {
1105         auto* env = getEnv();
1106 
1107         auto varString = LocalRef<jstring> ((jstring)env->CallObjectMethod (bundle, AndroidBundle.getString,
1108                                                                             javaString ("properties").get()));
1109 
1110         var resultVar;
1111         JSON::parse (juceString (varString.get()), resultVar);
1112 
1113         // Note: We are not checking if result of parsing was okay, because there may be no properties set at all.
1114         return resultVar;
1115     }
1116 
1117     // Reverse of juceNotificationToBundle().
localNotificationBundleToJuceNotificationjuce::PushNotifications::Pimpl1118     static PushNotifications::Notification localNotificationBundleToJuceNotification (const LocalRef<jobject>& bundle)
1119     {
1120         auto* env = getEnv();
1121 
1122         PushNotifications::Notification n;
1123 
1124         if (bundle.get() != nullptr)
1125         {
1126             n.identifier  = getStringFromBundle (env, "identifier", bundle);
1127             n.title       = getStringFromBundle (env, "title", bundle);
1128             n.body        = getStringFromBundle (env, "body", bundle);
1129             n.subtitle    = getStringFromBundle (env, "subtitle", bundle);
1130             n.badgeNumber = getIntFromBundle    (env, "badgeNumber", bundle);
1131             n.soundToPlay = URL (getStringFromBundle (env, "soundToPlay", bundle));
1132             n.properties  = getPropertiesVarFromBundle (env, "properties", bundle);
1133             n.tickerText  = getStringFromBundle (env, "tickerText", bundle);
1134             n.icon        = getStringFromBundle (env, "icon", bundle);
1135             n.channelId   = getStringFromBundle (env, "channelId", bundle);
1136 
1137             PushNotifications::Notification::Progress progress;
1138             progress.max           = getIntFromBundle  (env, "progressMax", bundle);
1139             progress.current       = getIntFromBundle  (env, "progressCurrent", bundle);
1140             progress.indeterminate = getBoolFromBundle (env, "progressIndeterminate", bundle);
1141             n.progress = progress;
1142 
1143             n.person       = getStringFromBundle (env, "person", bundle);
1144             n.type         = (PushNotifications::Notification::Type)     getIntFromBundle (env, "type", bundle);
1145             n.priority     = (PushNotifications::Notification::Priority) getIntFromBundle (env, "priority", bundle);
1146             n.lockScreenAppearance = (PushNotifications::Notification::LockScreenAppearance) getIntFromBundle (env, "lockScreenAppearance", bundle);
1147             n.groupId      = getStringFromBundle (env, "groupId", bundle);
1148             n.groupSortKey = getStringFromBundle (env, "groupSortKey", bundle);
1149             n.groupSummary = getBoolFromBundle   (env, "groupSummary", bundle);
1150             n.accentColour = Colour ((uint32) getIntFromBundle (env, "accentColour", bundle));
1151             n.ledColour    = Colour ((uint32) getIntFromBundle (env, "ledColour", bundle));
1152 
1153             PushNotifications::Notification::LedBlinkPattern ledBlinkPattern;
1154             ledBlinkPattern.msToBeOn  = getIntFromBundle (env, "ledBlinkPatternMsToBeOn", bundle);
1155             ledBlinkPattern.msToBeOff = getIntFromBundle (env, "ledBlinkPatternMsToBeOff", bundle);
1156             n.ledBlinkPattern = ledBlinkPattern;
1157 
1158             n.vibrationPattern = getLongArrayFromBundle (env, "vibrationPattern", bundle);
1159 
1160             n.shouldAutoCancel    = getBoolFromBundle (env, "shouldAutoCancel", bundle);
1161             n.localOnly           = getBoolFromBundle (env, "localOnly", bundle);
1162             n.ongoing             = getBoolFromBundle (env, "ongoing", bundle);
1163             n.alertOnlyOnce       = getBoolFromBundle (env, "alertOnlyOnce", bundle);
1164             n.timestampVisibility = (PushNotifications::Notification::TimestampVisibility) getIntFromBundle (env, "timestampVisibility", bundle);
1165             n.badgeIconType       = (PushNotifications::Notification::BadgeIconType) getIntFromBundle (env, "badgeIconType", bundle);
1166             n.groupAlertBehaviour = (PushNotifications::Notification::GroupAlertBehaviour) getIntFromBundle (env, "groupAlertBehaviour", bundle);
1167             n.timeoutAfterMs      = getLongFromBundle (env, "timeoutAfterMs", bundle);
1168         }
1169 
1170         return n;
1171     }
1172 
getStringFromBundlejuce::PushNotifications::Pimpl1173     static String getStringFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
1174     {
1175         auto keyString = javaString (key);
1176 
1177         if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
1178         {
1179             auto value = LocalRef<jstring> ((jstring)env->CallObjectMethod (bundle, AndroidBundle.getString, keyString.get()));
1180             return juceString (value);
1181         }
1182 
1183         return {};
1184     }
1185 
getIntFromBundlejuce::PushNotifications::Pimpl1186     static int getIntFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
1187     {
1188         auto keyString = javaString (key);
1189 
1190         if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
1191             return env->CallIntMethod (bundle, AndroidBundle.getInt, keyString.get());
1192 
1193         return 0;
1194     }
1195 
1196     // Converting to int on purpose!
getLongFromBundlejuce::PushNotifications::Pimpl1197     static int getLongFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
1198     {
1199         auto keyString = javaString (key);
1200 
1201         if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
1202             return (int) env->CallLongMethod (bundle, AndroidBundle.getLong, keyString.get());
1203 
1204         return 0;
1205     }
1206 
getPropertiesVarFromBundlejuce::PushNotifications::Pimpl1207     static var getPropertiesVarFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
1208     {
1209         auto keyString = javaString (key);
1210 
1211         if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
1212         {
1213             auto value = LocalRef<jobject> (env->CallObjectMethod (bundle, AndroidBundle.getBundle, keyString.get()));
1214             return bundleWithPropertiesStringToVar (value);
1215         }
1216 
1217         return {};
1218     }
1219 
getBoolFromBundlejuce::PushNotifications::Pimpl1220     static bool getBoolFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
1221     {
1222         auto keyString = javaString (key);
1223 
1224         if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
1225             return env->CallBooleanMethod (bundle, AndroidBundle.getBoolean, keyString.get());
1226 
1227         return false;
1228     }
1229 
getLongArrayFromBundlejuce::PushNotifications::Pimpl1230     static Array<int> getLongArrayFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
1231     {
1232         auto keyString = javaString (key);
1233 
1234         if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
1235         {
1236             auto array = LocalRef<jlongArray> ((jlongArray) env->CallObjectMethod (bundle, AndroidBundle.getLongArray, keyString.get()));
1237 
1238             const int size = env->GetArrayLength (array.get());
1239 
1240             jlong* elements = env->GetLongArrayElements (array.get(), nullptr);
1241 
1242             Array<int> resultArray;
1243 
1244             for (int i = 0; i < size; ++i)
1245                 resultArray.add ((int) *elements++);
1246 
1247             return resultArray;
1248         }
1249 
1250         return {};
1251     }
1252 
javaNotificationToJuceNotificationjuce::PushNotifications::Pimpl1253     static PushNotifications::Notification javaNotificationToJuceNotification (const LocalRef<jobject>& notification)
1254     {
1255         if (getAndroidSDKVersion() < 20)
1256             return {};
1257 
1258         auto* env = getEnv();
1259 
1260         auto extras = LocalRef<jobject> (env->GetObjectField (notification, AndroidNotification.extras));
1261         auto notificationData = LocalRef<jobject> (env->CallObjectMethod (extras, AndroidBundle.getBundle,
1262                                                                           javaString ("notificationData").get()));
1263 
1264         if (notificationData.get() != nullptr)
1265             return localNotificationBundleToJuceNotification (notificationData);
1266 
1267         return remoteNotificationBundleToJuceNotification (extras);
1268     }
1269 
remoteNotificationBundleToJuceNotificationjuce::PushNotifications::Pimpl1270     static PushNotifications::Notification remoteNotificationBundleToJuceNotification (const LocalRef<jobject>& bundle)
1271     {
1272         // This will probably work only for remote notifications that get delivered to system tray
1273         PushNotifications::Notification n;
1274         n.properties = bundleToVar (bundle);
1275 
1276         return n;
1277     }
1278 
bundleToVarjuce::PushNotifications::Pimpl1279     static var bundleToVar (const LocalRef<jobject>& bundle)
1280     {
1281         if (bundle.get() == nullptr)
1282         {
1283             auto* env = getEnv();
1284 
1285             auto keySet   = LocalRef<jobject> (env->CallObjectMethod (bundle, AndroidBundle.keySet));
1286             auto iterator = LocalRef<jobject> (env->CallObjectMethod (keySet, JavaSet.iterator));
1287 
1288             DynamicObject::Ptr dynamicObject = new DynamicObject();
1289 
1290             for (;;)
1291             {
1292                 if (! env->CallBooleanMethod (iterator, JavaIterator.hasNext))
1293                     break;
1294 
1295                 auto key            = LocalRef<jstring> ((jstring) env->CallObjectMethod (iterator, JavaIterator.next));
1296                 auto object         = LocalRef<jobject> (env->CallObjectMethod (bundle, AndroidBundle.get, key.get()));
1297 
1298                 if (object.get() != nullptr)
1299                 {
1300                     auto objectAsString = LocalRef<jstring> ((jstring) env->CallObjectMethod (object, JavaObject.toString));
1301                     auto objectClass    = LocalRef<jobject> (env->CallObjectMethod (object, JavaObject.getClass));
1302                     auto classAsString  = LocalRef<jstring> ((jstring) env->CallObjectMethod (objectClass, JavaClass.getName));
1303 
1304                     // Note: It seems that Firebase delivers values as strings always, so this check is rather unnecessary,
1305                     //       at least until they change the behaviour.
1306                     var value = juceString (classAsString) == "java.lang.Bundle" ? bundleToVar (object) : var (juceString (objectAsString.get()));
1307                     dynamicObject->setProperty (juceString (key.get()), value);
1308                 }
1309                 else
1310                 {
1311                     dynamicObject->setProperty (juceString (key.get()), {});
1312                 }
1313             }
1314 
1315             return var (dynamicObject.get());
1316         }
1317 
1318         return {};
1319     }
1320 
1321   #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
firebaseRemoteNotificationToJuceNotificationjuce::PushNotifications::Pimpl1322     static PushNotifications::Notification firebaseRemoteNotificationToJuceNotification (jobject remoteNotification)
1323     {
1324         auto* env = getEnv();
1325 
1326         auto collapseKey  = LocalRef<jstring> ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getCollapseKey));
1327         auto from         = LocalRef<jstring> ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getFrom));
1328         auto messageId    = LocalRef<jstring> ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getMessageId));
1329         auto messageType  = LocalRef<jstring> ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getMessageType));
1330         auto to           = LocalRef<jstring> ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getTo));
1331         auto notification = LocalRef<jobject> (env->CallObjectMethod (remoteNotification, RemoteMessage.getNotification));
1332         auto data         = LocalRef<jobject> (env->CallObjectMethod (remoteNotification, RemoteMessage.getData));
1333 
1334         const int64 sentTime = env->CallLongMethod (remoteNotification, RemoteMessage.getSentTime);
1335         const int ttl        = env->CallIntMethod  (remoteNotification, RemoteMessage.getTtl);
1336 
1337         auto keySet   = LocalRef<jobject> (env->CallObjectMethod (data, JavaMap.keySet));
1338         auto iterator = LocalRef<jobject> (env->CallObjectMethod (keySet, JavaSet.iterator));
1339 
1340         DynamicObject::Ptr dataDynamicObject = new DynamicObject();
1341 
1342         for (;;)
1343         {
1344             if (! env->CallBooleanMethod (iterator, JavaIterator.hasNext))
1345                 break;
1346 
1347             auto key   = LocalRef<jstring> ((jstring) env->CallObjectMethod (iterator, JavaIterator.next));
1348             auto value = LocalRef<jstring> ((jstring) env->CallObjectMethod (data, JavaMap.get, key.get()));
1349 
1350             dataDynamicObject->setProperty (juceString (key.get()), juceString (value.get()));
1351         }
1352 
1353         var dataVar (dataDynamicObject.get());
1354 
1355         DynamicObject::Ptr propertiesDynamicObject = new DynamicObject();
1356         propertiesDynamicObject->setProperty ("collapseKey", juceString (collapseKey.get()));
1357         propertiesDynamicObject->setProperty ("from", juceString (from.get()));
1358         propertiesDynamicObject->setProperty ("messageId", juceString (messageId.get()));
1359         propertiesDynamicObject->setProperty ("messageType", juceString (messageType.get()));
1360         propertiesDynamicObject->setProperty ("to", juceString (to.get()));
1361         propertiesDynamicObject->setProperty ("sentTime", sentTime);
1362         propertiesDynamicObject->setProperty ("ttl", ttl);
1363         propertiesDynamicObject->setProperty ("data", dataVar);
1364 
1365         PushNotifications::Notification n;
1366 
1367         if (notification != 0)
1368         {
1369             auto body                  = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getBody));
1370             auto bodyLocalizationKey   = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getBodyLocalizationKey));
1371             auto clickAction           = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getClickAction));
1372             auto color                 = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getColor));
1373             auto icon                  = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getIcon));
1374             auto sound                 = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getSound));
1375             auto tag                   = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTag));
1376             auto title                 = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTitle));
1377             auto titleLocalizationKey  = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTitleLocalizationKey));
1378             auto link                  = LocalRef<jobject> (env->CallObjectMethod (notification, RemoteMessageNotification.getLink));
1379 
1380             auto bodyLocalizationArgs  = LocalRef<jobjectArray> ((jobjectArray) env->CallObjectMethod (notification, RemoteMessageNotification.getBodyLocalizationArgs));
1381             auto titleLocalizationArgs = LocalRef<jobjectArray> ((jobjectArray) env->CallObjectMethod (notification, RemoteMessageNotification.getTitleLocalizationArgs));
1382 
1383             n.identifier = juceString (tag.get());
1384             n.title      = juceString (title.get());
1385             n.body       = juceString (body.get());
1386             n.soundToPlay = URL (juceString (sound.get()));
1387 
1388             auto colourString = juceString (color.get()).substring (1);
1389             const uint8 r = (uint8) colourString.substring (0, 2).getIntValue();
1390             const uint8 g = (uint8) colourString.substring (2, 4).getIntValue();
1391             const uint8 b = (uint8) colourString.substring (4, 6).getIntValue();
1392             n.accentColour = Colour (r, g, b);
1393 
1394             // Note: Ignoring the icon, because Firebase passes it as a string.
1395 
1396             propertiesDynamicObject->setProperty ("clickAction",           juceString (clickAction.get()));
1397             propertiesDynamicObject->setProperty ("bodyLocalizationKey",   juceString (bodyLocalizationKey.get()));
1398             propertiesDynamicObject->setProperty ("titleLocalizationKey",  juceString (titleLocalizationKey.get()));
1399             propertiesDynamicObject->setProperty ("bodyLocalizationArgs",  javaStringArrayToJuce (bodyLocalizationArgs));
1400             propertiesDynamicObject->setProperty ("titleLocalizationArgs", javaStringArrayToJuce (titleLocalizationArgs));
1401             propertiesDynamicObject->setProperty ("link",                  link.get() != nullptr ? juceString ((jstring) env->CallObjectMethod (link, AndroidUri.toString)) : String());
1402         }
1403 
1404         n.properties = var (propertiesDynamicObject.get());
1405 
1406         return n;
1407     }
1408   #endif
1409 
setupChannelsjuce::PushNotifications::Pimpl1410     void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels)
1411     {
1412         if (getAndroidSDKVersion() < 26)
1413             return;
1414 
1415         auto* env = getEnv();
1416 
1417         auto notificationManager = getNotificationManager();
1418 
1419         jassert (notificationManager.get() != nullptr);
1420 
1421         if (notificationManager.get() == nullptr)
1422             return;
1423 
1424         for (const auto& g : groups)
1425         {
1426             // Channel group identifier and name have to be set.
1427             jassert (g.identifier.isNotEmpty() && g.name.isNotEmpty());
1428 
1429             if (g.identifier.isNotEmpty() && g.name.isNotEmpty())
1430             {
1431                 auto group = LocalRef<jobject> (env->NewObject (NotificationChannelGroup, NotificationChannelGroup.constructor,
1432                                                                 javaString (g.identifier).get(), javaString (g.name).get()));
1433                 env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannelGroup, group.get());
1434             }
1435         }
1436 
1437         for (const auto& c : channels)
1438         {
1439             // Channel identifier, name and group have to be set.
1440             jassert (c.identifier.isNotEmpty() && c.name.isNotEmpty() && c.groupId.isNotEmpty());
1441 
1442             if (c.identifier.isEmpty() || c.name.isEmpty() || c.groupId.isEmpty())
1443                 continue;
1444 
1445             auto channel = LocalRef<jobject> (env->NewObject (NotificationChannel, NotificationChannel.constructor,
1446                                                               javaString (c.identifier).get(), javaString (c.name).get(), c.importance));
1447 
1448             env->CallVoidMethod (channel, NotificationChannel.enableLights,            c.enableLights);
1449             env->CallVoidMethod (channel, NotificationChannel.enableVibration,         c.enableVibration);
1450             env->CallVoidMethod (channel, NotificationChannel.setBypassDnd,            c.bypassDoNotDisturb);
1451             env->CallVoidMethod (channel, NotificationChannel.setDescription,          javaString (c.description).get());
1452             env->CallVoidMethod (channel, NotificationChannel.setGroup,                javaString (c.groupId).get());
1453             env->CallVoidMethod (channel, NotificationChannel.setImportance,           c.importance);
1454             env->CallVoidMethod (channel, NotificationChannel.setLightColor,           c.ledColour.getARGB());
1455             env->CallVoidMethod (channel, NotificationChannel.setLockscreenVisibility, c.lockScreenAppearance);
1456             env->CallVoidMethod (channel, NotificationChannel.setShowBadge,            c.canShowBadge);
1457 
1458 
1459             const int size = c.vibrationPattern.size();
1460 
1461             if (size > 0)
1462             {
1463                 auto array = LocalRef<jlongArray> (env->NewLongArray (size));
1464                 jlong* elements = env->GetLongArrayElements (array, nullptr);
1465 
1466                 for (int i = 0; i < size; ++i)
1467                     elements[i] = (jlong) c.vibrationPattern[i];
1468 
1469                 env->SetLongArrayRegion (array, 0, size, elements);
1470                 env->CallVoidMethod (channel, NotificationChannel.setVibrationPattern, array.get());
1471 
1472                 env->CallVoidMethod (channel, NotificationChannel.enableVibration, c.enableVibration);
1473             }
1474 
1475             LocalRef<jobject> builder (env->NewObject (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.constructor));
1476             const int contentTypeSonification = 4;
1477             const int usageNotification = 5;
1478             env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.setContentType, contentTypeSonification);
1479             env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.setUsage, usageNotification);
1480             auto audioAttributes = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.build));
1481             env->CallVoidMethod (channel, NotificationChannel.setSound, juceUrlToAndroidUri (c.soundToPlay).get(), audioAttributes.get());
1482 
1483             env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannel, channel.get());
1484         }
1485     }
1486 
getPendingLocalNotificationsjuce::PushNotifications::Pimpl1487     void getPendingLocalNotifications() const {}
removePendingLocalNotificationjuce::PushNotifications::Pimpl1488     void removePendingLocalNotification (const String&) {}
removeAllPendingLocalNotificationsjuce::PushNotifications::Pimpl1489     void removeAllPendingLocalNotifications() {}
1490 
intentActionContainsAnyOfjuce::PushNotifications::Pimpl1491     static bool intentActionContainsAnyOf (jobject intent, const StringArray& strings, bool includePackageName)
1492     {
1493         auto* env = getEnv();
1494         LocalRef<jobject> context (getMainActivity());
1495 
1496         String packageName = includePackageName ? juceString ((jstring) env->CallObjectMethod (context.get(),
1497                                                                                                AndroidContext.getPackageName))
1498                                                 : String{};
1499 
1500         String intentAction = juceString ((jstring) env->CallObjectMethod (intent, AndroidIntent.getAction));
1501 
1502         for (const auto& string : strings)
1503             if (intentAction.contains (packageName + string))
1504                 return true;
1505 
1506         return false;
1507     }
1508 
isDeleteNotificationIntentjuce::PushNotifications::Pimpl1509     static bool isDeleteNotificationIntent (jobject intent)
1510     {
1511         return intentActionContainsAnyOf (intent, StringArray (".JUCE_NOTIFICATION_DELETED"), true);
1512     }
1513 
isLocalNotificationIntentjuce::PushNotifications::Pimpl1514     static bool isLocalNotificationIntent (jobject intent)
1515     {
1516         return intentActionContainsAnyOf (intent, { ".JUCE_NOTIFICATION.",
1517                                                     ".JUCE_NOTIFICATION_BUTTON_ACTION.",
1518                                                     ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION." },
1519                                           true);
1520     }
1521 
isRemoteNotificationIntentjuce::PushNotifications::Pimpl1522     static bool isRemoteNotificationIntent (jobject intent)
1523     {
1524         auto* env = getEnv();
1525 
1526         auto categories = LocalRef<jobject> (env->CallObjectMethod (intent, AndroidIntent.getCategories));
1527 
1528         int categoriesNum = categories != nullptr
1529                           ? env->CallIntMethod (categories, JavaSet.size)
1530                           : 0;
1531 
1532         if (categoriesNum == 0)
1533             return false;
1534 
1535         if (! env->CallBooleanMethod (categories, JavaSet.contains, javaString ("android.intent.category.LAUNCHER").get()))
1536             return false;
1537 
1538         if (! intentActionContainsAnyOf (intent, StringArray ("android.intent.action.MAIN"), false))
1539             return false;
1540 
1541         auto extras = LocalRef<jobject> (env->CallObjectMethod (intent, AndroidIntent.getExtras));
1542 
1543         if (extras == nullptr)
1544             return false;
1545 
1546         return env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.sent_time").get())
1547             && env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.message_id").get());
1548     }
1549 
1550     PushNotifications& owner;
1551 };
1552 
1553 #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME)
1554 //==============================================================================
1555 struct JuceFirebaseInstanceIdService
1556 {
1557     #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
1558      CALLBACK (tokenRefreshed, "firebaseInstanceIdTokenRefreshed", "(Ljava/lang/String;)V")
1559 
1560      DECLARE_JNI_CLASS (InstanceIdService, "com/rmsl/juce/JuceFirebaseInstanceIdService")
1561     #undef JNI_CLASS_MEMBERS
1562 
tokenRefreshedjuce::JuceFirebaseInstanceIdService1563     static void JNICALL tokenRefreshed (JNIEnv*, jobject /*instanceIdService*/, void* token)
1564     {
1565         if (auto* instance = PushNotifications::getInstanceWithoutCreating())
1566             instance->pimpl->notifyListenersTokenRefreshed (juceString (static_cast<jstring> (token)));
1567     }
1568 };
1569 
1570 JuceFirebaseInstanceIdService::InstanceIdService_Class JuceFirebaseInstanceIdService::InstanceIdService;
1571 #endif
1572 
1573 #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
1574 //==============================================================================
1575 struct JuceFirebaseMessagingService
1576 {
1577     #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
1578      CALLBACK (remoteNotificationReceived,  "firebaseRemoteMessageReceived",  "(Lcom/google/firebase/messaging/RemoteMessage;)V") \
1579      CALLBACK (remoteMessagesDeleted,       "firebaseRemoteMessagesDeleted",  "()V") \
1580      CALLBACK (remoteMessageSent,           "firebaseRemoteMessageSent",      "(Ljava/lang/String;)V") \
1581      CALLBACK (remoteMessageSendError,      "firebaseRemoteMessageSendError", "(Ljava/lang/String;Ljava/lang/String;)V")
1582 
1583      DECLARE_JNI_CLASS (MessagingService, "com/rmsl/juce/JuceFirebaseMessagingService")
1584     #undef JNI_CLASS_MEMBERS
1585 
remoteNotificationReceivedjuce::JuceFirebaseMessagingService1586     static void JNICALL remoteNotificationReceived (JNIEnv*, jobject /*messagingService*/, void* remoteMessage)
1587     {
1588         if (auto* instance = PushNotifications::getInstanceWithoutCreating())
1589             instance->pimpl->notifyListenersAboutRemoteNotificationFromService (LocalRef<jobject> (static_cast<jobject> (remoteMessage)));
1590 
1591     }
1592 
remoteMessagesDeletedjuce::JuceFirebaseMessagingService1593     static void JNICALL remoteMessagesDeleted()
1594     {
1595         if (auto* instance = PushNotifications::getInstanceWithoutCreating())
1596             instance->pimpl->notifyListenersAboutRemoteNotificationsDeleted();
1597     }
1598 
remoteMessageSentjuce::JuceFirebaseMessagingService1599     static void JNICALL remoteMessageSent (JNIEnv*, jobject /*messagingService*/, void* messageId)
1600     {
1601         if (auto* instance = PushNotifications::getInstanceWithoutCreating())
1602             instance->pimpl->notifyListenersAboutUpstreamMessageSent (LocalRef<jstring> (static_cast<jstring> (messageId)));
1603     }
1604 
remoteMessageSendErrorjuce::JuceFirebaseMessagingService1605     static void JNICALL remoteMessageSendError (JNIEnv*, jobject /*messagingService*/, void* messageId, void* error)
1606     {
1607         if (auto* instance = PushNotifications::getInstanceWithoutCreating())
1608             instance->pimpl->notifyListenersAboutUpstreamMessageSendingError (LocalRef<jstring> (static_cast<jstring> (messageId)),
1609                                                                               LocalRef<jstring> (static_cast<jstring> (error)));
1610     }
1611 };
1612 
1613 JuceFirebaseMessagingService::MessagingService_Class  JuceFirebaseMessagingService::MessagingService;
1614 #endif
1615 
1616 //==============================================================================
juce_handleNotificationIntent(void * intent)1617 bool juce_handleNotificationIntent (void* intent)
1618 {
1619     auto* instance = PushNotifications::getInstanceWithoutCreating();
1620 
1621     if (PushNotifications::Pimpl::isDeleteNotificationIntent ((jobject) intent))
1622     {
1623         if (instance)
1624             instance->pimpl->notifyListenersAboutLocalNotificationDeleted (LocalRef<jobject> ((jobject) intent));
1625 
1626         return true;
1627     }
1628     else if (PushNotifications::Pimpl::isLocalNotificationIntent ((jobject) intent))
1629     {
1630         if (instance)
1631             instance->pimpl->notifyListenersAboutLocalNotification (LocalRef<jobject> ((jobject) intent));
1632 
1633         return true;
1634     }
1635   #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
1636     else if (PushNotifications::Pimpl::isRemoteNotificationIntent ((jobject) intent))
1637     {
1638         if (instance)
1639             instance->pimpl->notifyListenersAboutRemoteNotificationFromSystemTray (LocalRef<jobject> ((jobject) intent));
1640 
1641         return true;
1642     }
1643   #endif
1644 
1645     return false;
1646 }
1647 
1648 //==============================================================================
1649 struct JuceActivityNewIntentListener
1650 {
1651     #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
1652      CALLBACK (appNewIntent, "appNewIntent", "(Landroid/content/Intent;)V")
1653 
DECLARE_JNI_CLASSjuce::JuceActivityNewIntentListener1654      DECLARE_JNI_CLASS (JavaActivity, JUCE_PUSH_NOTIFICATIONS_ACTIVITY)
1655     #undef JNI_CLASS_MEMBERS
1656 
1657     static void JNICALL appNewIntent (JNIEnv*, jobject /*activity*/, jobject intentData)
1658     {
1659        #if JUCE_PUSH_NOTIFICATIONS && JUCE_MODULE_AVAILABLE_juce_gui_extra
1660         juce_handleNotificationIntent(static_cast<void*>(intentData));
1661        #else
1662         juce::ignoreUnused(intentData);
1663        #endif
1664     }
1665 };
1666 
1667 JuceActivityNewIntentListener::JavaActivity_Class JuceActivityNewIntentListener::JavaActivity;
1668 
1669 } // namespace juce
1670