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.

1670 lines
85KB

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