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