The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1628 lines
86KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  21. METHOD (constructor, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \
  22. METHOD (enableLights, "enableLights", "(Z)V") \
  23. METHOD (enableVibration, "enableVibration", "(Z)V") \
  24. METHOD (setBypassDnd, "setBypassDnd", "(Z)V") \
  25. METHOD (setDescription, "setDescription", "(Ljava/lang/String;)V") \
  26. METHOD (setGroup, "setGroup", "(Ljava/lang/String;)V") \
  27. METHOD (setImportance, "setImportance", "(I)V") \
  28. METHOD (setLightColor, "setLightColor", "(I)V") \
  29. METHOD (setLockscreenVisibility, "setLockscreenVisibility", "(I)V") \
  30. METHOD (setShowBadge, "setShowBadge", "(Z)V") \
  31. METHOD (setSound, "setSound", "(Landroid/net/Uri;Landroid/media/AudioAttributes;)V") \
  32. METHOD (setVibrationPattern, "setVibrationPattern", "([J)V")
  33. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationChannel, "android/app/NotificationChannel", 26)
  34. #undef JNI_CLASS_MEMBERS
  35. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  36. METHOD (constructor, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;)V")
  37. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationChannelGroup, "android/app/NotificationChannelGroup", 26)
  38. #undef JNI_CLASS_MEMBERS
  39. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  40. FIELD (extras, "extras", "Landroid/os/Bundle;")
  41. DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidNotification, "android/app/Notification", 19)
  42. #undef JNI_CLASS_MEMBERS
  43. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  44. METHOD (addExtras, "addExtras", "(Landroid/os/Bundle;)Landroid/app/Notification$Action$Builder;") \
  45. METHOD (addRemoteInput, "addRemoteInput", "(Landroid/app/RemoteInput;)Landroid/app/Notification$Action$Builder;") \
  46. METHOD (constructor, "<init>", "(ILjava/lang/CharSequence;Landroid/app/PendingIntent;)V") \
  47. METHOD (build, "build", "()Landroid/app/Notification$Action;")
  48. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationActionBuilder, "android/app/Notification$Action$Builder", 20)
  49. #undef JNI_CLASS_MEMBERS
  50. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  51. METHOD (getNotification, "getNotification", "()Landroid/app/Notification;") \
  52. METHOD (setAutoCancel, "setAutoCancel", "(Z)Landroid/app/Notification$Builder;") \
  53. METHOD (setContentInfo, "setContentInfo", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \
  54. METHOD (setContentIntent, "setContentIntent", "(Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \
  55. METHOD (setContentText, "setContentText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \
  56. METHOD (setContentTitle, "setContentTitle", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \
  57. METHOD (setDefaults, "setDefaults", "(I)Landroid/app/Notification$Builder;") \
  58. METHOD (setDeleteIntent, "setDeleteIntent", "(Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \
  59. METHOD (setLargeIcon, "setLargeIcon", "(Landroid/graphics/Bitmap;)Landroid/app/Notification$Builder;") \
  60. METHOD (setLights, "setLights", "(III)Landroid/app/Notification$Builder;") \
  61. METHOD (setNumber, "setNumber", "(I)Landroid/app/Notification$Builder;") \
  62. METHOD (setOngoing, "setOngoing", "(Z)Landroid/app/Notification$Builder;") \
  63. METHOD (setOnlyAlertOnce, "setOnlyAlertOnce", "(Z)Landroid/app/Notification$Builder;") \
  64. METHOD (setProgress, "setProgress", "(IIZ)Landroid/app/Notification$Builder;") \
  65. METHOD (setSmallIcon, "setSmallIcon", "(I)Landroid/app/Notification$Builder;") \
  66. METHOD (setSound, "setSound", "(Landroid/net/Uri;)Landroid/app/Notification$Builder;") \
  67. METHOD (setTicker, "setTicker", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \
  68. METHOD (setVibrate, "setVibrate", "([J)Landroid/app/Notification$Builder;") \
  69. METHOD (setWhen, "setWhen", "(J)Landroid/app/Notification$Builder;")
  70. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderBase, "android/app/Notification$Builder", 11)
  71. #undef JNI_CLASS_MEMBERS
  72. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  73. METHOD (addAction, "addAction", "(ILjava/lang/CharSequence;Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \
  74. METHOD (build, "build", "()Landroid/app/Notification;") \
  75. METHOD (setPriority, "setPriority", "(I)Landroid/app/Notification$Builder;") \
  76. METHOD (setSubText, "setSubText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \
  77. METHOD (setUsesChronometer, "setUsesChronometer", "(Z)Landroid/app/Notification$Builder;")
  78. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi16, "android/app/Notification$Builder", 16)
  79. #undef JNI_CLASS_MEMBERS
  80. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  81. METHOD (setShowWhen, "setShowWhen", "(Z)Landroid/app/Notification$Builder;")
  82. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi17, "android/app/Notification$Builder", 17)
  83. #undef JNI_CLASS_MEMBERS
  84. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  85. METHOD (addAction, "addAction", "(Landroid/app/Notification$Action;)Landroid/app/Notification$Builder;") \
  86. METHOD (addExtras, "addExtras", "(Landroid/os/Bundle;)Landroid/app/Notification$Builder;") \
  87. METHOD (setLocalOnly, "setLocalOnly", "(Z)Landroid/app/Notification$Builder;") \
  88. METHOD (setGroup, "setGroup", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \
  89. METHOD (setGroupSummary, "setGroupSummary", "(Z)Landroid/app/Notification$Builder;") \
  90. METHOD (setSortKey, "setSortKey", "(Ljava/lang/String;)Landroid/app/Notification$Builder;")
  91. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi20, "android/app/Notification$Builder", 20)
  92. #undef JNI_CLASS_MEMBERS
  93. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  94. METHOD (addPerson, "addPerson", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \
  95. METHOD (setCategory, "setCategory", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \
  96. METHOD (setColor, "setColor", "(I)Landroid/app/Notification$Builder;") \
  97. METHOD (setPublicVersion, "setPublicVersion", "(Landroid/app/Notification;)Landroid/app/Notification$Builder;") \
  98. METHOD (setVisibility, "setVisibility", "(I)Landroid/app/Notification$Builder;")
  99. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi21, "android/app/Notification$Builder", 21)
  100. #undef JNI_CLASS_MEMBERS
  101. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  102. METHOD (setChronometerCountDown, "setChronometerCountDown", "(Z)Landroid/app/Notification$Builder;")
  103. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi24, "android/app/Notification$Builder", 24)
  104. #undef JNI_CLASS_MEMBERS
  105. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  106. METHOD (setBadgeIconType, "setBadgeIconType", "(I)Landroid/app/Notification$Builder;") \
  107. METHOD (setGroupAlertBehavior, "setGroupAlertBehavior", "(I)Landroid/app/Notification$Builder;") \
  108. METHOD (setTimeoutAfter, "setTimeoutAfter", "(J)Landroid/app/Notification$Builder;")
  109. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi26, "android/app/Notification$Builder", 26)
  110. #undef JNI_CLASS_MEMBERS
  111. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  112. METHOD (cancel, "cancel", "(Ljava/lang/String;I)V") \
  113. METHOD (cancelAll, "cancelAll", "()V") \
  114. METHOD (notify, "notify", "(Ljava/lang/String;ILandroid/app/Notification;)V")
  115. DECLARE_JNI_CLASS (NotificationManagerBase, "android/app/NotificationManager")
  116. #undef JNI_CLASS_MEMBERS
  117. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  118. METHOD (getActiveNotifications, "getActiveNotifications", "()[Landroid/service/notification/StatusBarNotification;")
  119. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi23, "android/app/NotificationManager", 23)
  120. #undef JNI_CLASS_MEMBERS
  121. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  122. METHOD (areNotificationsEnabled, "areNotificationsEnabled", "()Z")
  123. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi24, "android/app/NotificationManager", 24)
  124. #undef JNI_CLASS_MEMBERS
  125. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  126. METHOD (createNotificationChannel, "createNotificationChannel", "(Landroid/app/NotificationChannel;)V") \
  127. METHOD (createNotificationChannelGroup, "createNotificationChannelGroup", "(Landroid/app/NotificationChannelGroup;)V")
  128. DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi26, "android/app/NotificationManager", 26)
  129. #undef JNI_CLASS_MEMBERS
  130. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  131. STATICMETHOD (getResultsFromIntent, "getResultsFromIntent", "(Landroid/content/Intent;)Landroid/os/Bundle;")
  132. DECLARE_JNI_CLASS_WITH_MIN_SDK (RemoteInput, "android/app/RemoteInput", 20)
  133. #undef JNI_CLASS_MEMBERS
  134. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  135. METHOD (constructor, "<init>", "(Ljava/lang/String;)V") \
  136. METHOD (build, "build", "()Landroid/app/RemoteInput;") \
  137. METHOD (setAllowFreeFormInput, "setAllowFreeFormInput", "(Z)Landroid/app/RemoteInput$Builder;") \
  138. METHOD (setChoices, "setChoices", "([Ljava/lang/CharSequence;)Landroid/app/RemoteInput$Builder;") \
  139. METHOD (setLabel, "setLabel", "(Ljava/lang/CharSequence;)Landroid/app/RemoteInput$Builder;")
  140. DECLARE_JNI_CLASS_WITH_MIN_SDK (RemoteInputBuilder, "android/app/RemoteInput$Builder", 20)
  141. #undef JNI_CLASS_MEMBERS
  142. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  143. METHOD (getNotification, "getNotification", "()Landroid/app/Notification;")
  144. DECLARE_JNI_CLASS_WITH_MIN_SDK (StatusBarNotification, "android/service/notification/StatusBarNotification", 23)
  145. #undef JNI_CLASS_MEMBERS
  146. //==============================================================================
  147. #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME)
  148. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  149. STATICMETHOD (getInstance, "getInstance", "()Lcom/google/firebase/iid/FirebaseInstanceId;") \
  150. METHOD (getToken, "getToken", "()Ljava/lang/String;")
  151. DECLARE_JNI_CLASS (FirebaseInstanceId, "com/google/firebase/iid/FirebaseInstanceId")
  152. #undef JNI_CLASS_MEMBERS
  153. #endif
  154. #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  155. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  156. STATICMETHOD (getInstance, "getInstance", "()Lcom/google/firebase/messaging/FirebaseMessaging;") \
  157. METHOD (send, "send", "(Lcom/google/firebase/messaging/RemoteMessage;)V") \
  158. METHOD (subscribeToTopic, "subscribeToTopic", "(Ljava/lang/String;)Lcom/google/android/gms/tasks/Task;") \
  159. METHOD (unsubscribeFromTopic, "unsubscribeFromTopic", "(Ljava/lang/String;)Lcom/google/android/gms/tasks/Task;") \
  160. DECLARE_JNI_CLASS (FirebaseMessaging, "com/google/firebase/messaging/FirebaseMessaging")
  161. #undef JNI_CLASS_MEMBERS
  162. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  163. METHOD (getCollapseKey, "getCollapseKey", "()Ljava/lang/String;") \
  164. METHOD (getData, "getData", "()Ljava/util/Map;") \
  165. METHOD (getFrom, "getFrom", "()Ljava/lang/String;") \
  166. METHOD (getMessageId, "getMessageId", "()Ljava/lang/String;") \
  167. METHOD (getMessageType, "getMessageType", "()Ljava/lang/String;") \
  168. METHOD (getNotification, "getNotification", "()Lcom/google/firebase/messaging/RemoteMessage$Notification;") \
  169. METHOD (getSentTime, "getSentTime", "()J") \
  170. METHOD (getTo, "getTo", "()Ljava/lang/String;") \
  171. METHOD (getTtl, "getTtl", "()I")
  172. DECLARE_JNI_CLASS (RemoteMessage, "com/google/firebase/messaging/RemoteMessage")
  173. #undef JNI_CLASS_MEMBERS
  174. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  175. METHOD (addData, "addData", "(Ljava/lang/String;Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \
  176. METHOD (build, "build", "()Lcom/google/firebase/messaging/RemoteMessage;") \
  177. METHOD (constructor, "<init>", "(Ljava/lang/String;)V") \
  178. METHOD (setCollapseKey, "setCollapseKey", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \
  179. METHOD (setMessageId, "setMessageId", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \
  180. METHOD (setMessageType, "setMessageType", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \
  181. METHOD (setTtl, "setTtl", "(I)Lcom/google/firebase/messaging/RemoteMessage$Builder;")
  182. DECLARE_JNI_CLASS (RemoteMessageBuilder, "com/google/firebase/messaging/RemoteMessage$Builder")
  183. #undef JNI_CLASS_MEMBERS
  184. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  185. METHOD (getBody, "getBody", "()Ljava/lang/String;") \
  186. METHOD (getBodyLocalizationArgs, "getBodyLocalizationArgs", "()[Ljava/lang/String;") \
  187. METHOD (getBodyLocalizationKey, "getBodyLocalizationKey", "()Ljava/lang/String;") \
  188. METHOD (getClickAction, "getClickAction", "()Ljava/lang/String;") \
  189. METHOD (getColor, "getColor", "()Ljava/lang/String;") \
  190. METHOD (getIcon, "getIcon", "()Ljava/lang/String;") \
  191. METHOD (getLink, "getLink", "()Landroid/net/Uri;") \
  192. METHOD (getSound, "getSound", "()Ljava/lang/String;") \
  193. METHOD (getTag, "getTag", "()Ljava/lang/String;") \
  194. METHOD (getTitle, "getTitle", "()Ljava/lang/String;") \
  195. METHOD (getTitleLocalizationArgs, "getTitleLocalizationArgs", "()[Ljava/lang/String;") \
  196. METHOD (getTitleLocalizationKey, "getTitleLocalizationKey", "()Ljava/lang/String;")
  197. DECLARE_JNI_CLASS (RemoteMessageNotification, "com/google/firebase/messaging/RemoteMessage$Notification")
  198. #undef JNI_CLASS_MEMBERS
  199. #endif
  200. //==============================================================================
  201. bool PushNotifications::Notification::isValid() const noexcept
  202. {
  203. bool isValidForPreApi26 = title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && icon.isNotEmpty();
  204. bool apiAtLeast26 = (getAndroidSDKVersion() >= 26);
  205. if (apiAtLeast26)
  206. return isValidForPreApi26 && channelId.isNotEmpty();
  207. return isValidForPreApi26;
  208. }
  209. //==============================================================================
  210. struct PushNotifications::Pimpl
  211. {
  212. Pimpl (PushNotifications& p)
  213. : owner (p)
  214. {}
  215. bool areNotificationsEnabled() const
  216. {
  217. if (getAndroidSDKVersion() >= 24)
  218. {
  219. auto* env = getEnv();
  220. auto notificationManager = getNotificationManager();
  221. if (notificationManager.get() != nullptr)
  222. return env->CallBooleanMethod (notificationManager, NotificationManagerApi24.areNotificationsEnabled);
  223. }
  224. return true;
  225. }
  226. //==============================================================================
  227. void sendLocalNotification (const PushNotifications::Notification& n)
  228. {
  229. // All required fields have to be setup!
  230. jassert (n.isValid());
  231. auto* env = getEnv();
  232. auto notificationManager = getNotificationManager();
  233. if (notificationManager.get() != nullptr)
  234. {
  235. auto notification = juceNotificationToJavaNotification (n);
  236. auto tag = javaString (n.identifier);
  237. const int id = 0;
  238. env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.notify, tag.get(), id, notification.get());
  239. }
  240. }
  241. void getDeliveredNotifications() const
  242. {
  243. if (getAndroidSDKVersion() >= 23)
  244. {
  245. auto* env = getEnv();
  246. Array<PushNotifications::Notification> notifications;
  247. auto notificationManager = getNotificationManager();
  248. jassert (notificationManager != nullptr);
  249. if (notificationManager.get() != nullptr)
  250. {
  251. auto statusBarNotifications = LocalRef<jobjectArray> ((jobjectArray)env->CallObjectMethod (notificationManager,
  252. NotificationManagerApi23.getActiveNotifications));
  253. const int numNotifications = env->GetArrayLength (statusBarNotifications.get());
  254. for (int i = 0; i < numNotifications; ++i)
  255. {
  256. auto statusBarNotification = LocalRef<jobject> (env->GetObjectArrayElement (statusBarNotifications.get(), (jsize) i));
  257. auto notification = LocalRef<jobject> (env->CallObjectMethod (statusBarNotification, StatusBarNotification.getNotification));
  258. notifications.add (javaNotificationToJuceNotification (notification));
  259. }
  260. }
  261. owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifications); });
  262. }
  263. else
  264. {
  265. // Not supported on this platform
  266. jassertfalse;
  267. owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); });
  268. }
  269. }
  270. void notifyListenersAboutLocalNotification (const LocalRef<jobject>& intent)
  271. {
  272. auto* env = getEnv();
  273. LocalRef<jobject> context (getMainActivity());
  274. auto bundle = LocalRef<jobject> (env->CallObjectMethod (intent, AndroidIntent.getExtras));
  275. const auto notification = localNotificationBundleToJuceNotification (bundle);
  276. auto packageName = juceString ((jstring) env->CallObjectMethod (context.get(), AndroidContext.getPackageName));
  277. String notificationString = packageName + ".JUCE_NOTIFICATION.";
  278. String notificationButtonActionString = packageName + ".JUCE_NOTIFICATION_BUTTON_ACTION.";
  279. String notificationTextInputActionString = packageName + ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION.";
  280. auto actionString = juceString ((jstring) env->CallObjectMethod (intent, AndroidIntent.getAction));
  281. if (actionString.contains (notificationString))
  282. {
  283. owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, notification); });
  284. }
  285. else if (actionString.contains (notificationButtonActionString))
  286. {
  287. auto prefix = notificationButtonActionString + notification.identifier + ".";
  288. auto actionTitle = actionString.fromLastOccurrenceOf (prefix, false, false) // skip prefix
  289. .fromFirstOccurrenceOf (".", false, false); // skip action index
  290. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, notification, actionTitle, {}); });
  291. }
  292. else if (getAndroidSDKVersion() >= 20 && actionString.contains (notificationTextInputActionString))
  293. {
  294. auto prefix = notificationTextInputActionString + notification.identifier + ".";
  295. auto actionTitle = actionString.fromLastOccurrenceOf (prefix, false, false) // skip prefix
  296. .fromFirstOccurrenceOf (".", false, false); // skip action index
  297. auto actionIndex = actionString.fromLastOccurrenceOf (prefix, false, false).upToFirstOccurrenceOf (".", false, false);
  298. auto resultKeyString = javaString (actionTitle + actionIndex);
  299. auto remoteInputResult = LocalRef<jobject> (env->CallStaticObjectMethod (RemoteInput, RemoteInput.getResultsFromIntent, intent.get()));
  300. String responseString;
  301. if (remoteInputResult.get() == nullptr)
  302. {
  303. auto charSequence = LocalRef<jobject> (env->CallObjectMethod (remoteInputResult, AndroidBundle.getCharSequence, resultKeyString.get()));
  304. auto responseStringRef = LocalRef<jstring> ((jstring) env->CallObjectMethod (charSequence, JavaCharSequence.toString));
  305. responseString = juceString (responseStringRef.get());
  306. }
  307. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, notification, actionTitle, responseString); });
  308. }
  309. }
  310. void notifyListenersAboutLocalNotificationDeleted (const LocalRef<jobject>& intent)
  311. {
  312. auto* env = getEnv();
  313. auto bundle = LocalRef<jobject> (env->CallObjectMethod (intent, AndroidIntent.getExtras));
  314. auto notification = localNotificationBundleToJuceNotification (bundle);
  315. owner.listeners.call ([&] (Listener& l) { l.localNotificationDismissedByUser (notification); });
  316. }
  317. void removeAllDeliveredNotifications()
  318. {
  319. auto* env = getEnv();
  320. auto notificationManager = getNotificationManager();
  321. if (notificationManager.get() != nullptr)
  322. env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.cancelAll);
  323. }
  324. void removeDeliveredNotification (const String& identifier)
  325. {
  326. auto* env = getEnv();
  327. auto notificationManager = getNotificationManager();
  328. if (notificationManager.get() != nullptr)
  329. {
  330. auto tag = javaString (identifier);
  331. const int id = 0;
  332. env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.cancel, tag.get(), id);
  333. }
  334. }
  335. //==============================================================================
  336. String getDeviceToken() const
  337. {
  338. #if defined (JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME)
  339. auto* env = getEnv();
  340. auto instanceId = LocalRef<jobject> (env->CallStaticObjectMethod (FirebaseInstanceId, FirebaseInstanceId.getInstance));
  341. return juceString ((jstring) env->CallObjectMethod (instanceId, FirebaseInstanceId.getToken));
  342. #else
  343. return {};
  344. #endif
  345. }
  346. void notifyListenersTokenRefreshed ([[maybe_unused]] const String& token)
  347. {
  348. #if defined (JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME)
  349. MessageManager::callAsync ([this, token]
  350. {
  351. owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (token); });
  352. });
  353. #endif
  354. }
  355. //==============================================================================
  356. void subscribeToTopic ([[maybe_unused]] const String& topic)
  357. {
  358. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  359. auto* env = getEnv();
  360. auto firebaseMessaging = LocalRef<jobject> (env->CallStaticObjectMethod (FirebaseMessaging,
  361. FirebaseMessaging.getInstance));
  362. env->CallObjectMethod (firebaseMessaging, FirebaseMessaging.subscribeToTopic, javaString (topic).get());
  363. #endif
  364. }
  365. void unsubscribeFromTopic ([[maybe_unused]] const String& topic)
  366. {
  367. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  368. auto* env = getEnv();
  369. auto firebaseMessaging = LocalRef<jobject> (env->CallStaticObjectMethod (FirebaseMessaging,
  370. FirebaseMessaging.getInstance));
  371. env->CallObjectMethod (firebaseMessaging, FirebaseMessaging.unsubscribeFromTopic, javaString (topic).get());
  372. #endif
  373. }
  374. void sendUpstreamMessage ([[maybe_unused]] const String& serverSenderId,
  375. [[maybe_unused]] const String& collapseKey,
  376. [[maybe_unused]] const String& messageId,
  377. [[maybe_unused]] const String& messageType,
  378. [[maybe_unused]] int timeToLive,
  379. [[maybe_unused]] const StringPairArray& additionalData)
  380. {
  381. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  382. auto* env = getEnv();
  383. auto messageBuilder = LocalRef<jobject> (env->NewObject (RemoteMessageBuilder,
  384. RemoteMessageBuilder.constructor,
  385. javaString (serverSenderId + "@gcm_googleapis.com").get()));
  386. env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setCollapseKey, javaString (collapseKey).get());
  387. env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setMessageId, javaString (messageId).get());
  388. env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setMessageType, javaString (messageType).get());
  389. env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setTtl, timeToLive);
  390. auto keys = additionalData.getAllKeys();
  391. for (const auto& key : keys)
  392. env->CallObjectMethod (messageBuilder,
  393. RemoteMessageBuilder.addData,
  394. javaString (key).get(),
  395. javaString (additionalData[key]).get());
  396. auto message = LocalRef<jobject> (env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.build));
  397. auto firebaseMessaging = LocalRef<jobject> (env->CallStaticObjectMethod (FirebaseMessaging,
  398. FirebaseMessaging.getInstance));
  399. env->CallVoidMethod (firebaseMessaging, FirebaseMessaging.send, message.get());
  400. #endif
  401. }
  402. void notifyListenersAboutRemoteNotificationFromSystemTray ([[maybe_unused]] const LocalRef<jobject>& intent)
  403. {
  404. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  405. auto* env = getEnv();
  406. auto bundle = LocalRef<jobject> (env->CallObjectMethod (intent, AndroidIntent.getExtras));
  407. auto notification = remoteNotificationBundleToJuceNotification (bundle);
  408. owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, notification); });
  409. #endif
  410. }
  411. void notifyListenersAboutRemoteNotificationFromService ([[maybe_unused]] const LocalRef<jobject>& remoteNotification)
  412. {
  413. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  414. GlobalRef rn (remoteNotification);
  415. MessageManager::callAsync ([this, rn]
  416. {
  417. auto notification = firebaseRemoteNotificationToJuceNotification (rn.get());
  418. owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, notification); });
  419. });
  420. #endif
  421. }
  422. void notifyListenersAboutRemoteNotificationsDeleted()
  423. {
  424. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  425. MessageManager::callAsync ([this]
  426. {
  427. owner.listeners.call ([] (Listener& l) { l.remoteNotificationsDeleted(); });
  428. });
  429. #endif
  430. }
  431. void notifyListenersAboutUpstreamMessageSent ([[maybe_unused]] const LocalRef<jstring>& messageId)
  432. {
  433. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  434. GlobalRef mid (LocalRef<jobject> (messageId.get()));
  435. MessageManager::callAsync ([this, mid]
  436. {
  437. auto midString = juceString ((jstring) mid.get());
  438. owner.listeners.call ([&] (Listener& l) { l.upstreamMessageSent (midString); });
  439. });
  440. #endif
  441. }
  442. void notifyListenersAboutUpstreamMessageSendingError ([[maybe_unused]] const LocalRef<jstring>& messageId,
  443. [[maybe_unused]] const LocalRef<jstring>& error)
  444. {
  445. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  446. GlobalRef mid (LocalRef<jobject> (messageId.get())), e (LocalRef<jobject> (error.get()));
  447. MessageManager::callAsync ([this, mid, e]
  448. {
  449. auto midString = juceString ((jstring) mid.get());
  450. auto eString = juceString ((jstring) e.get());
  451. owner.listeners.call ([&] (Listener& l) { l.upstreamMessageSendingError (midString, eString); });
  452. });
  453. #endif
  454. }
  455. static LocalRef<jobject> getNotificationManager()
  456. {
  457. auto* env = getEnv();
  458. LocalRef<jobject> context (getMainActivity());
  459. return LocalRef<jobject> (env->CallObjectMethod (context.get(),
  460. AndroidContext.getSystemService,
  461. javaString ("notification").get()));
  462. }
  463. static LocalRef<jobject> juceNotificationToJavaNotification (const PushNotifications::Notification& n)
  464. {
  465. auto* env = getEnv();
  466. auto notificationBuilder = createNotificationBuilder (n);
  467. setupRequiredFields (n, notificationBuilder);
  468. setupOptionalFields (n, notificationBuilder);
  469. if (n.actions.size() > 0)
  470. setupActions (n, notificationBuilder);
  471. if (getAndroidSDKVersion() >= 16)
  472. return LocalRef<jobject> (env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.build));
  473. return LocalRef<jobject> (env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.getNotification));
  474. }
  475. static LocalRef<jobject> createNotificationBuilder (const PushNotifications::Notification& n)
  476. {
  477. auto* env = getEnv();
  478. LocalRef<jobject> context (getMainActivity());
  479. jclass builderClass = env->FindClass ("android/app/Notification$Builder");
  480. jassert (builderClass != nullptr);
  481. if (builderClass == nullptr)
  482. return LocalRef<jobject> (nullptr);
  483. jmethodID builderConstructor = nullptr;
  484. const bool apiAtLeast26 = (getAndroidSDKVersion() >= 26);
  485. if (apiAtLeast26)
  486. builderConstructor = env->GetMethodID (builderClass, "<init>", "(Landroid/content/Context;Ljava/lang/String;)V");
  487. else
  488. builderConstructor = env->GetMethodID (builderClass, "<init>", "(Landroid/content/Context;)V");
  489. jassert (builderConstructor != nullptr);
  490. if (builderConstructor == nullptr)
  491. return LocalRef<jobject> (nullptr);
  492. if (apiAtLeast26)
  493. return LocalRef<jobject> (env->NewObject (builderClass, builderConstructor,
  494. context.get(), javaString (n.channelId).get()));
  495. return LocalRef<jobject> (env->NewObject (builderClass, builderConstructor, context.get()));
  496. }
  497. static void setupRequiredFields (const PushNotifications::Notification& n, LocalRef<jobject>& notificationBuilder)
  498. {
  499. auto* env = getEnv();
  500. LocalRef<jobject> context (getMainActivity());
  501. auto activityClass = LocalRef<jobject> (env->CallObjectMethod (context.get(), JavaObject.getClass));
  502. auto notifyIntent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get()));
  503. auto packageNameString = LocalRef<jstring> ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName)));
  504. auto actionStringSuffix = javaString (".JUCE_NOTIFICATION." + n.identifier);
  505. auto actionString = LocalRef<jstring> ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get()));
  506. env->CallObjectMethod (notifyIntent, AndroidIntent.setAction, actionString.get());
  507. // Packaging entire notification into extras bundle here, so that we can retrieve all the details later on
  508. env->CallObjectMethod (notifyIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get());
  509. auto notifyPendingIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidPendingIntent,
  510. AndroidPendingIntent.getActivity,
  511. context.get(),
  512. 1002,
  513. notifyIntent.get(),
  514. 0));
  515. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentTitle, javaString (n.title).get());
  516. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentText, javaString (n.body).get());
  517. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentIntent, notifyPendingIntent.get());
  518. auto resources = LocalRef<jobject> (env->CallObjectMethod (context.get(), AndroidContext.getResources));
  519. const int iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(),
  520. javaString ("raw").get(), packageNameString.get());
  521. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setSmallIcon, iconId);
  522. if (getAndroidSDKVersion() >= 21 && n.publicVersion != nullptr)
  523. {
  524. // Public version of a notification is not expected to have another public one!
  525. jassert (n.publicVersion->publicVersion == nullptr);
  526. auto publicNotificationBuilder = createNotificationBuilder (n);
  527. setupRequiredFields (*n.publicVersion, publicNotificationBuilder);
  528. setupOptionalFields (*n.publicVersion, publicNotificationBuilder);
  529. auto publicVersion = LocalRef<jobject> (env->CallObjectMethod (publicNotificationBuilder, NotificationBuilderApi16.build));
  530. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setPublicVersion, publicVersion.get());
  531. }
  532. }
  533. static LocalRef<jobject> juceNotificationToBundle (const PushNotifications::Notification& n)
  534. {
  535. auto* env = getEnv();
  536. auto bundle = LocalRef<jobject> (env->NewObject (AndroidBundle, AndroidBundle.constructor));
  537. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("identifier") .get(), javaString (n.identifier).get());
  538. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("title") .get(), javaString (n.title).get());
  539. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("body") .get(), javaString (n.body).get());
  540. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("subtitle") .get(), javaString (n.subtitle).get());
  541. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("badgeNumber") .get(), n.badgeNumber);
  542. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("soundToPlay") .get(), javaString (n.soundToPlay.toString (true)).get());
  543. env->CallVoidMethod (bundle, AndroidBundle.putBundle, javaString ("properties") .get(), varToBundleWithPropertiesString (n.properties).get());
  544. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("icon") .get(), javaString (n.icon).get());
  545. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("channelId") .get(), javaString (n.channelId).get());
  546. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("tickerText") .get(), javaString (n.tickerText).get());
  547. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("progressMax") .get(), n.progress.max);
  548. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("progressCurrent") .get(), n.progress.current);
  549. env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("progressIndeterminate") .get(), n.progress.indeterminate);
  550. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("person") .get(), javaString (n.person).get());
  551. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("type") .get(), n.type);
  552. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("priority") .get(), n.priority);
  553. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("lockScreenAppearance") .get(), n.lockScreenAppearance);
  554. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("groupId") .get(), javaString (n.groupId).get());
  555. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("groupSortKey") .get(), javaString (n.groupSortKey).get());
  556. env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("groupSummary") .get(), n.groupSummary);
  557. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("accentColour") .get(), n.accentColour.getARGB());
  558. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("ledColour") .get(), n.ledColour.getARGB());
  559. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("ledBlinkPatternMsToBeOn") .get(), n.ledBlinkPattern.msToBeOn);
  560. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("ledBlinkPatternMsToBeOff").get(), n.ledBlinkPattern.msToBeOff);
  561. env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("shouldAutoCancel") .get(), n.shouldAutoCancel);
  562. env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("localOnly") .get(), n.localOnly);
  563. env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("ongoing") .get(), n.ongoing);
  564. env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("alertOnlyOnce") .get(), n.alertOnlyOnce);
  565. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("timestampVisibility") .get(), n.timestampVisibility);
  566. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("badgeIconType") .get(), n.badgeIconType);
  567. env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("groupAlertBehaviour") .get(), n.groupAlertBehaviour);
  568. env->CallVoidMethod (bundle, AndroidBundle.putLong, javaString ("timeoutAfterMs") .get(), (jlong)n.timeoutAfterMs);
  569. const int size = n.vibrationPattern.size();
  570. if (size > 0)
  571. {
  572. auto array = LocalRef<jlongArray> (env->NewLongArray (size));
  573. jlong* elements = env->GetLongArrayElements (array, nullptr);
  574. for (int i = 0; i < size; ++i)
  575. elements[i] = (jlong) n.vibrationPattern[i];
  576. env->SetLongArrayRegion (array, 0, size, elements);
  577. env->CallVoidMethod (bundle, AndroidBundle.putLongArray, javaString ("vibrationPattern").get(), array.get());
  578. }
  579. return bundle;
  580. }
  581. static void setupOptionalFields (const PushNotifications::Notification& n, LocalRef<jobject>& notificationBuilder)
  582. {
  583. auto* env = getEnv();
  584. if (n.subtitle.isNotEmpty())
  585. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentInfo, javaString (n.subtitle).get());
  586. auto soundName = n.soundToPlay.toString (true);
  587. if (soundName == "default_os_sound")
  588. {
  589. const int playDefaultSound = 1;
  590. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDefaults, playDefaultSound);
  591. }
  592. else if (! soundName.isEmpty())
  593. {
  594. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setSound, juceUrlToAndroidUri (n.soundToPlay).get());
  595. }
  596. if (n.largeIcon.isValid())
  597. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setLargeIcon, imagetoJavaBitmap (n.largeIcon).get());
  598. if (n.tickerText.isNotEmpty())
  599. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setTicker, javaString (n.tickerText).get());
  600. if (n.ledColour != Colour())
  601. {
  602. env->CallObjectMethod (notificationBuilder,
  603. NotificationBuilderBase.setLights,
  604. n.ledColour.getARGB(),
  605. n.ledBlinkPattern.msToBeOn,
  606. n.ledBlinkPattern.msToBeOff);
  607. }
  608. if (! n.vibrationPattern.isEmpty())
  609. {
  610. const int size = n.vibrationPattern.size();
  611. if (size > 0)
  612. {
  613. auto array = LocalRef<jlongArray> (env->NewLongArray (size));
  614. jlong* elements = env->GetLongArrayElements (array, nullptr);
  615. for (int i = 0; i < size; ++i)
  616. elements[i] = (jlong) n.vibrationPattern[i];
  617. env->SetLongArrayRegion (array, 0, size, elements);
  618. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setVibrate, array.get());
  619. }
  620. }
  621. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setProgress, n.progress.max, n.progress.current, n.progress.indeterminate);
  622. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setNumber, n.badgeNumber);
  623. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setAutoCancel, n.shouldAutoCancel);
  624. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setOngoing, n.ongoing);
  625. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setOnlyAlertOnce, n.alertOnlyOnce);
  626. if (getAndroidSDKVersion() >= 16)
  627. {
  628. if (n.subtitle.isNotEmpty())
  629. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setSubText, javaString (n.subtitle).get());
  630. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setPriority, n.priority);
  631. if (getAndroidSDKVersion() < 24)
  632. {
  633. const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer;
  634. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer);
  635. }
  636. }
  637. if (getAndroidSDKVersion() >= 17)
  638. {
  639. const bool showTimeStamp = n.timestampVisibility != PushNotifications::Notification::off;
  640. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi17.setShowWhen, showTimeStamp);
  641. }
  642. if (getAndroidSDKVersion() >= 20)
  643. {
  644. if (n.groupId.isNotEmpty())
  645. {
  646. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroup, javaString (n.groupId).get());
  647. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroupSummary, n.groupSummary);
  648. }
  649. if (n.groupSortKey.isNotEmpty())
  650. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setSortKey, javaString (n.groupSortKey).get());
  651. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setLocalOnly, n.localOnly);
  652. auto extras = LocalRef<jobject> (env->NewObject (AndroidBundle, AndroidBundle.constructor));
  653. env->CallVoidMethod (extras, AndroidBundle.putBundle, javaString ("notificationData").get(),
  654. juceNotificationToBundle (n).get());
  655. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addExtras, extras.get());
  656. }
  657. if (getAndroidSDKVersion() >= 21)
  658. {
  659. if (n.person.isNotEmpty())
  660. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.addPerson, javaString (n.person).get());
  661. auto categoryString = typeToCategory (n.type);
  662. if (categoryString.isNotEmpty())
  663. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setCategory, javaString (categoryString).get());
  664. if (n.accentColour != Colour())
  665. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setColor, n.accentColour.getARGB());
  666. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setVisibility, n.lockScreenAppearance);
  667. }
  668. if (getAndroidSDKVersion() >= 24)
  669. {
  670. const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer;
  671. const bool useCountDownChronometer = n.timestampVisibility == PushNotifications::Notification::countDownChronometer;
  672. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi24.setChronometerCountDown, useCountDownChronometer);
  673. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer | useCountDownChronometer);
  674. }
  675. if (getAndroidSDKVersion() >= 26)
  676. {
  677. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setBadgeIconType, n.badgeIconType);
  678. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setGroupAlertBehavior, n.groupAlertBehaviour);
  679. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setTimeoutAfter, (jlong) n.timeoutAfterMs);
  680. }
  681. setupNotificationDeletedCallback (n, notificationBuilder);
  682. }
  683. static void setupNotificationDeletedCallback (const PushNotifications::Notification& n,
  684. LocalRef<jobject>& notificationBuilder)
  685. {
  686. auto* env = getEnv();
  687. LocalRef<jobject> context (getMainActivity());
  688. auto activityClass = LocalRef<jobject> (env->CallObjectMethod (context.get(), JavaObject.getClass));
  689. auto deleteIntent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get()));
  690. auto packageNameString = LocalRef<jstring> ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName)));
  691. auto actionStringSuffix = javaString (".JUCE_NOTIFICATION_DELETED." + n.identifier);
  692. auto actionString = LocalRef<jstring> ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get()));
  693. env->CallObjectMethod (deleteIntent, AndroidIntent.setAction, actionString.get());
  694. env->CallObjectMethod (deleteIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get());
  695. auto deletePendingIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidPendingIntent,
  696. AndroidPendingIntent.getActivity,
  697. context.get(),
  698. 1002,
  699. deleteIntent.get(),
  700. 0));
  701. env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDeleteIntent, deletePendingIntent.get());
  702. }
  703. static void setupActions (const PushNotifications::Notification& n, LocalRef<jobject>& notificationBuilder)
  704. {
  705. if (getAndroidSDKVersion() < 16)
  706. return;
  707. auto* env = getEnv();
  708. LocalRef<jobject> context (getMainActivity());
  709. int actionIndex = 0;
  710. for (const auto& action : n.actions)
  711. {
  712. auto activityClass = LocalRef<jobject> (env->CallObjectMethod (context.get(), JavaObject.getClass));
  713. auto notifyIntent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get()));
  714. const bool isTextStyle = action.style == PushNotifications::Notification::Action::text;
  715. auto packageNameString = LocalRef<jstring> ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName)));
  716. const String notificationActionString = isTextStyle ? ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION." : ".JUCE_NOTIFICATION_BUTTON_ACTION.";
  717. auto actionStringSuffix = javaString (notificationActionString + n.identifier + "." + String (actionIndex) + "." + action.title);
  718. auto actionString = LocalRef<jstring> ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get()));
  719. env->CallObjectMethod (notifyIntent, AndroidIntent.setAction, actionString.get());
  720. // Packaging entire notification into extras bundle here, so that we can retrieve all the details later on
  721. env->CallObjectMethod (notifyIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get());
  722. auto notifyPendingIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidPendingIntent,
  723. AndroidPendingIntent.getActivity,
  724. context.get(),
  725. 1002,
  726. notifyIntent.get(),
  727. 0));
  728. auto resources = LocalRef<jobject> (env->CallObjectMethod (context.get(), AndroidContext.getResources));
  729. int iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (action.icon).get(),
  730. javaString ("raw").get(), packageNameString.get());
  731. if (iconId == 0)
  732. iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(),
  733. javaString ("raw").get(), packageNameString.get());
  734. if (getAndroidSDKVersion() >= 20)
  735. {
  736. auto actionBuilder = LocalRef<jobject> (env->NewObject (NotificationActionBuilder,
  737. NotificationActionBuilder.constructor,
  738. iconId,
  739. javaString (action.title).get(),
  740. notifyPendingIntent.get()));
  741. env->CallObjectMethod (actionBuilder, NotificationActionBuilder.addExtras,
  742. varToBundleWithPropertiesString (action.parameters).get());
  743. if (isTextStyle)
  744. {
  745. auto resultKey = javaString (action.title + String (actionIndex));
  746. auto remoteInputBuilder = LocalRef<jobject> (env->NewObject (RemoteInputBuilder,
  747. RemoteInputBuilder.constructor,
  748. resultKey.get()));
  749. if (! action.textInputPlaceholder.isEmpty())
  750. env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setLabel, javaString (action.textInputPlaceholder).get());
  751. if (! action.allowedResponses.isEmpty())
  752. {
  753. env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setAllowFreeFormInput, false);
  754. const int size = action.allowedResponses.size();
  755. auto array = LocalRef<jobjectArray> (env->NewObjectArray (size, env->FindClass ("java/lang/String"), nullptr));
  756. for (int i = 0; i < size; ++i)
  757. {
  758. const auto& response = action.allowedResponses[i];
  759. auto responseString = javaString (response);
  760. env->SetObjectArrayElement (array, i, responseString.get());
  761. }
  762. env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setChoices, array.get());
  763. }
  764. env->CallObjectMethod (actionBuilder, NotificationActionBuilder.addRemoteInput,
  765. env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.build));
  766. }
  767. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addAction,
  768. env->CallObjectMethod (actionBuilder, NotificationActionBuilder.build));
  769. }
  770. else
  771. {
  772. env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.addAction,
  773. iconId, javaString (action.title).get(), notifyPendingIntent.get());
  774. }
  775. ++actionIndex;
  776. }
  777. }
  778. static LocalRef<jobject> juceUrlToAndroidUri (const URL& url)
  779. {
  780. auto* env = getEnv();
  781. LocalRef<jobject> context (getMainActivity());
  782. auto packageNameString = LocalRef<jstring> ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName)));
  783. auto resources = LocalRef<jobject> (env->CallObjectMethod (context.get(), AndroidContext.getResources));
  784. const int id = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (url.toString (true)).get(),
  785. javaString ("raw").get(), packageNameString.get());
  786. auto schemeString = javaString ("android.resource://");
  787. auto resourceString = javaString ("/" + String (id));
  788. auto uriString = LocalRef<jstring> ((jstring) env->CallObjectMethod (schemeString, JavaString.concat, packageNameString.get()));
  789. uriString = LocalRef<jstring> ((jstring) env->CallObjectMethod (uriString, JavaString.concat, resourceString.get()));
  790. return LocalRef<jobject> (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, uriString.get()));
  791. }
  792. static LocalRef<jobject> imagetoJavaBitmap (const Image& image)
  793. {
  794. auto* env = getEnv();
  795. Image imageToUse = image.convertedToFormat (Image::PixelFormat::ARGB);
  796. auto bitmapConfig = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidBitmapConfig,
  797. AndroidBitmapConfig.valueOf,
  798. javaString ("ARGB_8888").get()));
  799. auto bitmap = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidBitmap,
  800. AndroidBitmap.createBitmap,
  801. image.getWidth(),
  802. image.getHeight(),
  803. bitmapConfig.get()));
  804. for (int i = 0; i < image.getWidth(); ++i)
  805. for (int j = 0; j < image.getHeight(); ++j)
  806. env->CallVoidMethod (bitmap.get(), AndroidBitmap.setPixel, i, j, image.getPixelAt (i, j).getARGB());
  807. return bitmap;
  808. }
  809. static String typeToCategory (PushNotifications::Notification::Type t)
  810. {
  811. switch (t)
  812. {
  813. case PushNotifications::Notification::unspecified: return {};
  814. case PushNotifications::Notification::alarm: return "alarm";
  815. case PushNotifications::Notification::call: return "call";
  816. case PushNotifications::Notification::email: return "email";
  817. case PushNotifications::Notification::error: return "err";
  818. case PushNotifications::Notification::event: return "event";
  819. case PushNotifications::Notification::message: return "msg";
  820. case PushNotifications::Notification::taskProgress: return "progress";
  821. case PushNotifications::Notification::promo: return "promo";
  822. case PushNotifications::Notification::recommendation: return "recommendation";
  823. case PushNotifications::Notification::reminder: return "reminder";
  824. case PushNotifications::Notification::service: return "service";
  825. case PushNotifications::Notification::social: return "social";
  826. case PushNotifications::Notification::status: return "status";
  827. case PushNotifications::Notification::system: return "sys";
  828. case PushNotifications::Notification::transport: return "transport";
  829. }
  830. return {};
  831. }
  832. static LocalRef<jobject> varToBundleWithPropertiesString (const var& varToParse)
  833. {
  834. auto* env = getEnv();
  835. auto bundle = LocalRef<jobject> (env->NewObject (AndroidBundle, AndroidBundle.constructor));
  836. env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("properties").get(),
  837. javaString (JSON::toString (varToParse)).get());
  838. return bundle;
  839. }
  840. // Gets "properties" var from bundle.
  841. static var bundleWithPropertiesStringToVar (const LocalRef<jobject>& bundle)
  842. {
  843. auto* env = getEnv();
  844. auto varString = LocalRef<jstring> ((jstring)env->CallObjectMethod (bundle, AndroidBundle.getString,
  845. javaString ("properties").get()));
  846. var resultVar;
  847. JSON::parse (juceString (varString.get()), resultVar);
  848. // Note: We are not checking if result of parsing was okay, because there may be no properties set at all.
  849. return resultVar;
  850. }
  851. // Reverse of juceNotificationToBundle().
  852. static PushNotifications::Notification localNotificationBundleToJuceNotification (const LocalRef<jobject>& bundle)
  853. {
  854. auto* env = getEnv();
  855. PushNotifications::Notification n;
  856. if (bundle.get() != nullptr)
  857. {
  858. n.identifier = getStringFromBundle (env, "identifier", bundle);
  859. n.title = getStringFromBundle (env, "title", bundle);
  860. n.body = getStringFromBundle (env, "body", bundle);
  861. n.subtitle = getStringFromBundle (env, "subtitle", bundle);
  862. n.badgeNumber = getIntFromBundle (env, "badgeNumber", bundle);
  863. n.soundToPlay = URL (getStringFromBundle (env, "soundToPlay", bundle));
  864. n.properties = getPropertiesVarFromBundle (env, "properties", bundle);
  865. n.tickerText = getStringFromBundle (env, "tickerText", bundle);
  866. n.icon = getStringFromBundle (env, "icon", bundle);
  867. n.channelId = getStringFromBundle (env, "channelId", bundle);
  868. PushNotifications::Notification::Progress progress;
  869. progress.max = getIntFromBundle (env, "progressMax", bundle);
  870. progress.current = getIntFromBundle (env, "progressCurrent", bundle);
  871. progress.indeterminate = getBoolFromBundle (env, "progressIndeterminate", bundle);
  872. n.progress = progress;
  873. n.person = getStringFromBundle (env, "person", bundle);
  874. n.type = (PushNotifications::Notification::Type) getIntFromBundle (env, "type", bundle);
  875. n.priority = (PushNotifications::Notification::Priority) getIntFromBundle (env, "priority", bundle);
  876. n.lockScreenAppearance = (PushNotifications::Notification::LockScreenAppearance) getIntFromBundle (env, "lockScreenAppearance", bundle);
  877. n.groupId = getStringFromBundle (env, "groupId", bundle);
  878. n.groupSortKey = getStringFromBundle (env, "groupSortKey", bundle);
  879. n.groupSummary = getBoolFromBundle (env, "groupSummary", bundle);
  880. n.accentColour = Colour ((uint32) getIntFromBundle (env, "accentColour", bundle));
  881. n.ledColour = Colour ((uint32) getIntFromBundle (env, "ledColour", bundle));
  882. PushNotifications::Notification::LedBlinkPattern ledBlinkPattern;
  883. ledBlinkPattern.msToBeOn = getIntFromBundle (env, "ledBlinkPatternMsToBeOn", bundle);
  884. ledBlinkPattern.msToBeOff = getIntFromBundle (env, "ledBlinkPatternMsToBeOff", bundle);
  885. n.ledBlinkPattern = ledBlinkPattern;
  886. n.vibrationPattern = getLongArrayFromBundle (env, "vibrationPattern", bundle);
  887. n.shouldAutoCancel = getBoolFromBundle (env, "shouldAutoCancel", bundle);
  888. n.localOnly = getBoolFromBundle (env, "localOnly", bundle);
  889. n.ongoing = getBoolFromBundle (env, "ongoing", bundle);
  890. n.alertOnlyOnce = getBoolFromBundle (env, "alertOnlyOnce", bundle);
  891. n.timestampVisibility = (PushNotifications::Notification::TimestampVisibility) getIntFromBundle (env, "timestampVisibility", bundle);
  892. n.badgeIconType = (PushNotifications::Notification::BadgeIconType) getIntFromBundle (env, "badgeIconType", bundle);
  893. n.groupAlertBehaviour = (PushNotifications::Notification::GroupAlertBehaviour) getIntFromBundle (env, "groupAlertBehaviour", bundle);
  894. n.timeoutAfterMs = getLongFromBundle (env, "timeoutAfterMs", bundle);
  895. }
  896. return n;
  897. }
  898. static String getStringFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
  899. {
  900. auto keyString = javaString (key);
  901. if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
  902. {
  903. auto value = LocalRef<jstring> ((jstring)env->CallObjectMethod (bundle, AndroidBundle.getString, keyString.get()));
  904. return juceString (value);
  905. }
  906. return {};
  907. }
  908. static int getIntFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
  909. {
  910. auto keyString = javaString (key);
  911. if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
  912. return env->CallIntMethod (bundle, AndroidBundle.getInt, keyString.get());
  913. return 0;
  914. }
  915. // Converting to int on purpose!
  916. static int getLongFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
  917. {
  918. auto keyString = javaString (key);
  919. if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
  920. return (int) env->CallLongMethod (bundle, AndroidBundle.getLong, keyString.get());
  921. return 0;
  922. }
  923. static var getPropertiesVarFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
  924. {
  925. auto keyString = javaString (key);
  926. if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
  927. {
  928. auto value = LocalRef<jobject> (env->CallObjectMethod (bundle, AndroidBundle.getBundle, keyString.get()));
  929. return bundleWithPropertiesStringToVar (value);
  930. }
  931. return {};
  932. }
  933. static bool getBoolFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
  934. {
  935. auto keyString = javaString (key);
  936. if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
  937. return env->CallBooleanMethod (bundle, AndroidBundle.getBoolean, keyString.get());
  938. return false;
  939. }
  940. static Array<int> getLongArrayFromBundle (JNIEnv* env, const String& key, const LocalRef<jobject>& bundle)
  941. {
  942. auto keyString = javaString (key);
  943. if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get()))
  944. {
  945. auto array = LocalRef<jlongArray> ((jlongArray) env->CallObjectMethod (bundle, AndroidBundle.getLongArray, keyString.get()));
  946. const int size = env->GetArrayLength (array.get());
  947. jlong* elements = env->GetLongArrayElements (array.get(), nullptr);
  948. Array<int> resultArray;
  949. for (int i = 0; i < size; ++i)
  950. resultArray.add ((int) *elements++);
  951. return resultArray;
  952. }
  953. return {};
  954. }
  955. static PushNotifications::Notification javaNotificationToJuceNotification (const LocalRef<jobject>& notification)
  956. {
  957. if (getAndroidSDKVersion() < 20)
  958. return {};
  959. auto* env = getEnv();
  960. auto extras = LocalRef<jobject> (env->GetObjectField (notification, AndroidNotification.extras));
  961. auto notificationData = LocalRef<jobject> (env->CallObjectMethod (extras, AndroidBundle.getBundle,
  962. javaString ("notificationData").get()));
  963. if (notificationData.get() != nullptr)
  964. return localNotificationBundleToJuceNotification (notificationData);
  965. return remoteNotificationBundleToJuceNotification (extras);
  966. }
  967. static PushNotifications::Notification remoteNotificationBundleToJuceNotification (const LocalRef<jobject>& bundle)
  968. {
  969. // This will probably work only for remote notifications that get delivered to system tray
  970. PushNotifications::Notification n;
  971. n.properties = bundleToVar (bundle);
  972. return n;
  973. }
  974. static var bundleToVar (const LocalRef<jobject>& bundle)
  975. {
  976. if (bundle.get() == nullptr)
  977. {
  978. auto* env = getEnv();
  979. auto keySet = LocalRef<jobject> (env->CallObjectMethod (bundle, AndroidBundle.keySet));
  980. auto iterator = LocalRef<jobject> (env->CallObjectMethod (keySet, JavaSet.iterator));
  981. DynamicObject::Ptr dynamicObject = new DynamicObject();
  982. for (;;)
  983. {
  984. if (! env->CallBooleanMethod (iterator, JavaIterator.hasNext))
  985. break;
  986. auto key = LocalRef<jstring> ((jstring) env->CallObjectMethod (iterator, JavaIterator.next));
  987. auto object = LocalRef<jobject> (env->CallObjectMethod (bundle, AndroidBundle.get, key.get()));
  988. if (object.get() != nullptr)
  989. {
  990. auto objectAsString = LocalRef<jstring> ((jstring) env->CallObjectMethod (object, JavaObject.toString));
  991. auto objectClass = LocalRef<jobject> (env->CallObjectMethod (object, JavaObject.getClass));
  992. auto classAsString = LocalRef<jstring> ((jstring) env->CallObjectMethod (objectClass, JavaClass.getName));
  993. // Note: It seems that Firebase delivers values as strings always, so this check is rather unnecessary,
  994. // at least until they change the behaviour.
  995. var value = juceString (classAsString) == "java.lang.Bundle" ? bundleToVar (object) : var (juceString (objectAsString.get()));
  996. dynamicObject->setProperty (juceString (key.get()), value);
  997. }
  998. else
  999. {
  1000. dynamicObject->setProperty (juceString (key.get()), {});
  1001. }
  1002. }
  1003. return var (dynamicObject.get());
  1004. }
  1005. return {};
  1006. }
  1007. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  1008. static PushNotifications::Notification firebaseRemoteNotificationToJuceNotification (jobject remoteNotification)
  1009. {
  1010. auto* env = getEnv();
  1011. auto collapseKey = LocalRef<jstring> ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getCollapseKey));
  1012. auto from = LocalRef<jstring> ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getFrom));
  1013. auto messageId = LocalRef<jstring> ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getMessageId));
  1014. auto messageType = LocalRef<jstring> ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getMessageType));
  1015. auto to = LocalRef<jstring> ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getTo));
  1016. auto notification = LocalRef<jobject> (env->CallObjectMethod (remoteNotification, RemoteMessage.getNotification));
  1017. auto data = LocalRef<jobject> (env->CallObjectMethod (remoteNotification, RemoteMessage.getData));
  1018. const int64 sentTime = env->CallLongMethod (remoteNotification, RemoteMessage.getSentTime);
  1019. const int ttl = env->CallIntMethod (remoteNotification, RemoteMessage.getTtl);
  1020. auto keySet = LocalRef<jobject> (env->CallObjectMethod (data, JavaMap.keySet));
  1021. auto iterator = LocalRef<jobject> (env->CallObjectMethod (keySet, JavaSet.iterator));
  1022. DynamicObject::Ptr dataDynamicObject = new DynamicObject();
  1023. for (;;)
  1024. {
  1025. if (! env->CallBooleanMethod (iterator, JavaIterator.hasNext))
  1026. break;
  1027. auto key = LocalRef<jstring> ((jstring) env->CallObjectMethod (iterator, JavaIterator.next));
  1028. auto value = LocalRef<jstring> ((jstring) env->CallObjectMethod (data, JavaMap.get, key.get()));
  1029. dataDynamicObject->setProperty (juceString (key.get()), juceString (value.get()));
  1030. }
  1031. var dataVar (dataDynamicObject.get());
  1032. DynamicObject::Ptr propertiesDynamicObject = new DynamicObject();
  1033. propertiesDynamicObject->setProperty ("collapseKey", juceString (collapseKey.get()));
  1034. propertiesDynamicObject->setProperty ("from", juceString (from.get()));
  1035. propertiesDynamicObject->setProperty ("messageId", juceString (messageId.get()));
  1036. propertiesDynamicObject->setProperty ("messageType", juceString (messageType.get()));
  1037. propertiesDynamicObject->setProperty ("to", juceString (to.get()));
  1038. propertiesDynamicObject->setProperty ("sentTime", sentTime);
  1039. propertiesDynamicObject->setProperty ("ttl", ttl);
  1040. propertiesDynamicObject->setProperty ("data", dataVar);
  1041. PushNotifications::Notification n;
  1042. if (notification != 0)
  1043. {
  1044. auto body = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getBody));
  1045. auto bodyLocalizationKey = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getBodyLocalizationKey));
  1046. auto clickAction = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getClickAction));
  1047. auto color = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getColor));
  1048. auto icon = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getIcon));
  1049. auto sound = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getSound));
  1050. auto tag = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTag));
  1051. auto title = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTitle));
  1052. auto titleLocalizationKey = LocalRef<jstring> ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTitleLocalizationKey));
  1053. auto link = LocalRef<jobject> (env->CallObjectMethod (notification, RemoteMessageNotification.getLink));
  1054. auto bodyLocalizationArgs = LocalRef<jobjectArray> ((jobjectArray) env->CallObjectMethod (notification, RemoteMessageNotification.getBodyLocalizationArgs));
  1055. auto titleLocalizationArgs = LocalRef<jobjectArray> ((jobjectArray) env->CallObjectMethod (notification, RemoteMessageNotification.getTitleLocalizationArgs));
  1056. n.identifier = juceString (tag.get());
  1057. n.title = juceString (title.get());
  1058. n.body = juceString (body.get());
  1059. n.soundToPlay = URL (juceString (sound.get()));
  1060. auto colourString = juceString (color.get()).substring (1);
  1061. const uint8 r = (uint8) colourString.substring (0, 2).getIntValue();
  1062. const uint8 g = (uint8) colourString.substring (2, 4).getIntValue();
  1063. const uint8 b = (uint8) colourString.substring (4, 6).getIntValue();
  1064. n.accentColour = Colour (r, g, b);
  1065. // Note: Ignoring the icon, because Firebase passes it as a string.
  1066. propertiesDynamicObject->setProperty ("clickAction", juceString (clickAction.get()));
  1067. propertiesDynamicObject->setProperty ("bodyLocalizationKey", juceString (bodyLocalizationKey.get()));
  1068. propertiesDynamicObject->setProperty ("titleLocalizationKey", juceString (titleLocalizationKey.get()));
  1069. propertiesDynamicObject->setProperty ("bodyLocalizationArgs", javaStringArrayToJuce (bodyLocalizationArgs));
  1070. propertiesDynamicObject->setProperty ("titleLocalizationArgs", javaStringArrayToJuce (titleLocalizationArgs));
  1071. propertiesDynamicObject->setProperty ("link", link.get() != nullptr ? juceString ((jstring) env->CallObjectMethod (link, AndroidUri.toString)) : String());
  1072. }
  1073. n.properties = var (propertiesDynamicObject.get());
  1074. return n;
  1075. }
  1076. #endif
  1077. void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels)
  1078. {
  1079. if (getAndroidSDKVersion() < 26)
  1080. return;
  1081. auto* env = getEnv();
  1082. auto notificationManager = getNotificationManager();
  1083. jassert (notificationManager.get() != nullptr);
  1084. if (notificationManager.get() == nullptr)
  1085. return;
  1086. for (const auto& g : groups)
  1087. {
  1088. // Channel group identifier and name have to be set.
  1089. jassert (g.identifier.isNotEmpty() && g.name.isNotEmpty());
  1090. if (g.identifier.isNotEmpty() && g.name.isNotEmpty())
  1091. {
  1092. auto group = LocalRef<jobject> (env->NewObject (NotificationChannelGroup, NotificationChannelGroup.constructor,
  1093. javaString (g.identifier).get(), javaString (g.name).get()));
  1094. env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannelGroup, group.get());
  1095. }
  1096. }
  1097. for (const auto& c : channels)
  1098. {
  1099. // Channel identifier, name and group have to be set.
  1100. jassert (c.identifier.isNotEmpty() && c.name.isNotEmpty() && c.groupId.isNotEmpty());
  1101. if (c.identifier.isEmpty() || c.name.isEmpty() || c.groupId.isEmpty())
  1102. continue;
  1103. auto channel = LocalRef<jobject> (env->NewObject (NotificationChannel, NotificationChannel.constructor,
  1104. javaString (c.identifier).get(), javaString (c.name).get(), c.importance));
  1105. env->CallVoidMethod (channel, NotificationChannel.enableLights, c.enableLights);
  1106. env->CallVoidMethod (channel, NotificationChannel.enableVibration, c.enableVibration);
  1107. env->CallVoidMethod (channel, NotificationChannel.setBypassDnd, c.bypassDoNotDisturb);
  1108. env->CallVoidMethod (channel, NotificationChannel.setDescription, javaString (c.description).get());
  1109. env->CallVoidMethod (channel, NotificationChannel.setGroup, javaString (c.groupId).get());
  1110. env->CallVoidMethod (channel, NotificationChannel.setImportance, c.importance);
  1111. env->CallVoidMethod (channel, NotificationChannel.setLightColor, c.ledColour.getARGB());
  1112. env->CallVoidMethod (channel, NotificationChannel.setLockscreenVisibility, c.lockScreenAppearance);
  1113. env->CallVoidMethod (channel, NotificationChannel.setShowBadge, c.canShowBadge);
  1114. const int size = c.vibrationPattern.size();
  1115. if (size > 0)
  1116. {
  1117. auto array = LocalRef<jlongArray> (env->NewLongArray (size));
  1118. jlong* elements = env->GetLongArrayElements (array, nullptr);
  1119. for (int i = 0; i < size; ++i)
  1120. elements[i] = (jlong) c.vibrationPattern[i];
  1121. env->SetLongArrayRegion (array, 0, size, elements);
  1122. env->CallVoidMethod (channel, NotificationChannel.setVibrationPattern, array.get());
  1123. env->CallVoidMethod (channel, NotificationChannel.enableVibration, c.enableVibration);
  1124. }
  1125. LocalRef<jobject> builder (env->NewObject (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.constructor));
  1126. const int contentTypeSonification = 4;
  1127. const int usageNotification = 5;
  1128. env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.setContentType, contentTypeSonification);
  1129. env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.setUsage, usageNotification);
  1130. auto audioAttributes = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.build));
  1131. env->CallVoidMethod (channel, NotificationChannel.setSound, juceUrlToAndroidUri (c.soundToPlay).get(), audioAttributes.get());
  1132. env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannel, channel.get());
  1133. }
  1134. }
  1135. void getPendingLocalNotifications() const {}
  1136. void removePendingLocalNotification (const String&) {}
  1137. void removeAllPendingLocalNotifications() {}
  1138. static bool intentActionContainsAnyOf (jobject intent, const StringArray& strings, bool includePackageName)
  1139. {
  1140. auto* env = getEnv();
  1141. LocalRef<jobject> context (getMainActivity());
  1142. String packageName = includePackageName ? juceString ((jstring) env->CallObjectMethod (context.get(),
  1143. AndroidContext.getPackageName))
  1144. : String{};
  1145. String intentAction = juceString ((jstring) env->CallObjectMethod (intent, AndroidIntent.getAction));
  1146. for (const auto& string : strings)
  1147. if (intentAction.contains (packageName + string))
  1148. return true;
  1149. return false;
  1150. }
  1151. static bool isDeleteNotificationIntent (jobject intent)
  1152. {
  1153. return intentActionContainsAnyOf (intent, StringArray (".JUCE_NOTIFICATION_DELETED"), true);
  1154. }
  1155. static bool isLocalNotificationIntent (jobject intent)
  1156. {
  1157. return intentActionContainsAnyOf (intent, { ".JUCE_NOTIFICATION.",
  1158. ".JUCE_NOTIFICATION_BUTTON_ACTION.",
  1159. ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION." },
  1160. true);
  1161. }
  1162. static bool isRemoteNotificationIntent (jobject intent)
  1163. {
  1164. auto* env = getEnv();
  1165. auto categories = LocalRef<jobject> (env->CallObjectMethod (intent, AndroidIntent.getCategories));
  1166. int categoriesNum = categories != nullptr
  1167. ? env->CallIntMethod (categories, JavaSet.size)
  1168. : 0;
  1169. if (categoriesNum == 0)
  1170. return false;
  1171. if (! env->CallBooleanMethod (categories, JavaSet.contains, javaString ("android.intent.category.LAUNCHER").get()))
  1172. return false;
  1173. if (! intentActionContainsAnyOf (intent, StringArray ("android.intent.action.MAIN"), false))
  1174. return false;
  1175. auto extras = LocalRef<jobject> (env->CallObjectMethod (intent, AndroidIntent.getExtras));
  1176. if (extras == nullptr)
  1177. return false;
  1178. return env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.sent_time").get())
  1179. && env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.message_id").get());
  1180. }
  1181. PushNotifications& owner;
  1182. };
  1183. #if defined (JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME)
  1184. //==============================================================================
  1185. struct JuceFirebaseInstanceIdService
  1186. {
  1187. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  1188. CALLBACK (tokenRefreshed, "firebaseInstanceIdTokenRefreshed", "(Ljava/lang/String;)V")
  1189. DECLARE_JNI_CLASS (InstanceIdService, "com/rmsl/juce/JuceFirebaseInstanceIdService")
  1190. #undef JNI_CLASS_MEMBERS
  1191. static void JNICALL tokenRefreshed (JNIEnv*, jobject /*instanceIdService*/, void* token)
  1192. {
  1193. if (auto* instance = PushNotifications::getInstanceWithoutCreating())
  1194. instance->pimpl->notifyListenersTokenRefreshed (juceString (static_cast<jstring> (token)));
  1195. }
  1196. };
  1197. #endif
  1198. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  1199. //==============================================================================
  1200. struct JuceFirebaseMessagingService
  1201. {
  1202. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  1203. CALLBACK (remoteNotificationReceived, "firebaseRemoteMessageReceived", "(Lcom/google/firebase/messaging/RemoteMessage;)V") \
  1204. CALLBACK (remoteMessagesDeleted, "firebaseRemoteMessagesDeleted", "()V") \
  1205. CALLBACK (remoteMessageSent, "firebaseRemoteMessageSent", "(Ljava/lang/String;)V") \
  1206. CALLBACK (remoteMessageSendError, "firebaseRemoteMessageSendError", "(Ljava/lang/String;Ljava/lang/String;)V")
  1207. DECLARE_JNI_CLASS (MessagingService, "com/rmsl/juce/JuceFirebaseMessagingService")
  1208. #undef JNI_CLASS_MEMBERS
  1209. static void JNICALL remoteNotificationReceived (JNIEnv*, jobject /*messagingService*/, void* remoteMessage)
  1210. {
  1211. if (auto* instance = PushNotifications::getInstanceWithoutCreating())
  1212. instance->pimpl->notifyListenersAboutRemoteNotificationFromService (LocalRef<jobject> (static_cast<jobject> (remoteMessage)));
  1213. }
  1214. static void JNICALL remoteMessagesDeleted()
  1215. {
  1216. if (auto* instance = PushNotifications::getInstanceWithoutCreating())
  1217. instance->pimpl->notifyListenersAboutRemoteNotificationsDeleted();
  1218. }
  1219. static void JNICALL remoteMessageSent (JNIEnv*, jobject /*messagingService*/, void* messageId)
  1220. {
  1221. if (auto* instance = PushNotifications::getInstanceWithoutCreating())
  1222. instance->pimpl->notifyListenersAboutUpstreamMessageSent (LocalRef<jstring> (static_cast<jstring> (messageId)));
  1223. }
  1224. static void JNICALL remoteMessageSendError (JNIEnv*, jobject /*messagingService*/, void* messageId, void* error)
  1225. {
  1226. if (auto* instance = PushNotifications::getInstanceWithoutCreating())
  1227. instance->pimpl->notifyListenersAboutUpstreamMessageSendingError (LocalRef<jstring> (static_cast<jstring> (messageId)),
  1228. LocalRef<jstring> (static_cast<jstring> (error)));
  1229. }
  1230. };
  1231. #endif
  1232. //==============================================================================
  1233. bool juce_handleNotificationIntent (void* intent)
  1234. {
  1235. auto* instance = PushNotifications::getInstanceWithoutCreating();
  1236. if (PushNotifications::Pimpl::isDeleteNotificationIntent ((jobject) intent))
  1237. {
  1238. if (instance)
  1239. instance->pimpl->notifyListenersAboutLocalNotificationDeleted (LocalRef<jobject> ((jobject) intent));
  1240. return true;
  1241. }
  1242. else if (PushNotifications::Pimpl::isLocalNotificationIntent ((jobject) intent))
  1243. {
  1244. if (instance)
  1245. instance->pimpl->notifyListenersAboutLocalNotification (LocalRef<jobject> ((jobject) intent));
  1246. return true;
  1247. }
  1248. #if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
  1249. else if (PushNotifications::Pimpl::isRemoteNotificationIntent ((jobject) intent))
  1250. {
  1251. if (instance)
  1252. instance->pimpl->notifyListenersAboutRemoteNotificationFromSystemTray (LocalRef<jobject> ((jobject) intent));
  1253. return true;
  1254. }
  1255. #endif
  1256. return false;
  1257. }
  1258. } // namespace juce