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.

1671 lines
87KB

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