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.

1639 lines
83KB

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