Audio plugin host https://kx.studio/carla
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.

1663 lines
87KB

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