Skip to content

Commit 1fe7d5e

Browse files
authored
Merge pull request #203 from Unity-Technologies/ensure-unique-generated-id-remake
Scheduling tracking refactoring and unique ids (remake)
2 parents 46d18a7 + 8f5df4d commit 1fe7d5e

File tree

4 files changed

+188
-107
lines changed

4 files changed

+188
-107
lines changed

com.unity.mobile.notifications/Runtime/Android/AndroidNotificationCenter.cs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ void CollectMethods(AndroidJavaClass clazz)
114114
setNotificationGroupAlertBehavior = JniApi.FindMethod(clazz, "setNotificationGroupAlertBehavior", "(Landroid/app/Notification$Builder;I)V", true);
115115
getNotificationGroupAlertBehavior = JniApi.FindMethod(clazz, "getNotificationGroupAlertBehavior", "(Landroid/app/Notification;)I", true);
116116
getNotificationChannelId = JniApi.FindMethod(clazz, "getNotificationChannelId", "(Landroid/app/Notification;)Ljava/lang/String;", true);
117-
scheduleNotification = JniApi.FindMethod(clazz, "scheduleNotification", "(Landroid/app/Notification$Builder;)V", false);
117+
scheduleNotification = JniApi.FindMethod(clazz, "scheduleNotification", "(Landroid/app/Notification$Builder;)I", false);
118118
createNotificationBuilder = JniApi.FindMethod(clazz, "createNotificationBuilder", "(Ljava/lang/String;)Landroid/app/Notification$Builder;", false);
119119
}
120120

@@ -195,9 +195,9 @@ public void DeleteNotificationChannel(string channelId)
195195
self.Call("deleteNotificationChannel", channelId);
196196
}
197197

198-
public void ScheduleNotification(AndroidJavaObject notificationBuilder)
198+
public int ScheduleNotification(AndroidJavaObject notificationBuilder)
199199
{
200-
self.Call(scheduleNotification, notificationBuilder);
200+
return self.Call<int>(scheduleNotification, notificationBuilder);
201201
}
202202

203203
public bool CheckIfPendingNotificationIsRegistered(int id)
@@ -680,12 +680,9 @@ public static int SendNotification(AndroidNotification notification, string chan
680680
if (!Initialize())
681681
return -1;
682682

683-
// Now.ToString("yyMMddHHmmssffffff"), but avoiding any culture-related formatting or dependencies
684-
var now = DateTime.UtcNow;
685-
var nowFormatted = $"{now.Year}{now.Month}{now.Day}{now.Hour}{now.Minute}{now.Second}{now.Millisecond}";
686-
int id = Math.Abs(nowFormatted.GetHashCode()) + (new System.Random().Next(10000));
687-
using (var builder = CreateNotificationBuilder(id, notification, channelId))
688-
SendNotification(builder);
683+
int id;
684+
using (var builder = CreateNotificationBuilder(notification, channelId))
685+
SendNotification(builder, out id);
689686

690687
return id;
691688
}
@@ -716,6 +713,18 @@ public static void SendNotification(AndroidJavaObject notificationBuilder)
716713
s_Jni.NotificationManager.ScheduleNotification(notificationBuilder);
717714
}
718715

716+
/// <summary>
717+
/// Schedule a notification created using the provided Notification.Builder object.
718+
/// Notification builder should be created by calling CreateNotificationBuilder.
719+
/// Stores the notification id to the second argument
720+
/// </summary>
721+
public static void SendNotification(AndroidJavaObject notificationBuilder, out int id)
722+
{
723+
id = -1;
724+
if (Initialize())
725+
id = s_Jni.NotificationManager.ScheduleNotification(notificationBuilder);
726+
}
727+
719728
/// <summary>
720729
/// Update an already scheduled notification.
721730
/// If a notification with the specified id was already scheduled it will be overridden with the information from the passed notification struct.
@@ -860,8 +869,11 @@ public static void OpenNotificationSettings(string channelId = null)
860869
/// </summary>
861870
public static AndroidJavaObject CreateNotificationBuilder(AndroidNotification notification, string channelId)
862871
{
863-
int id = Math.Abs(DateTime.Now.ToString("yyMMddHHmmssffffff").GetHashCode()) + (new System.Random().Next(10000));
864-
return CreateNotificationBuilder(id, notification, channelId);
872+
AndroidJavaObject builder, extras;
873+
CreateNotificationBuilder(notification, channelId, out builder, out extras);
874+
if (extras != null)
875+
extras.Dispose();
876+
return builder;
865877
}
866878

867879
/// <summary>
@@ -873,6 +885,24 @@ public static AndroidJavaObject CreateNotificationBuilder(AndroidNotification no
873885
/// <returns>A proxy object for created Notification.Builder</returns>
874886
public static AndroidJavaObject CreateNotificationBuilder(int id, AndroidNotification notification, string channelId)
875887
{
888+
AndroidJavaObject builder, extras;
889+
CreateNotificationBuilder(notification, channelId, out builder, out extras);
890+
if (extras != null)
891+
{
892+
s_Jni.Bundle.PutInt(extras, s_Jni.NotificationManager.KEY_ID, id);
893+
extras.Dispose();
894+
}
895+
return builder;
896+
}
897+
898+
static void CreateNotificationBuilder(AndroidNotification notification, string channelId, out AndroidJavaObject notificationBuilder, out AndroidJavaObject extras)
899+
{
900+
if (!Initialize())
901+
{
902+
notificationBuilder = extras = null;
903+
return;
904+
}
905+
876906
long fireTime = notification.FireTime.ToLong();
877907
if (fireTime < 0L)
878908
{
@@ -881,7 +911,7 @@ public static AndroidJavaObject CreateNotificationBuilder(int id, AndroidNotific
881911

882912
// NOTE: JNI calls are expensive, so we avoid calls that set something that is also a default
883913

884-
var notificationBuilder = s_Jni.NotificationManager.CreateNotificationBuilder(channelId);
914+
notificationBuilder = s_Jni.NotificationManager.CreateNotificationBuilder(channelId);
885915
s_Jni.NotificationManager.SetNotificationIcon(notificationBuilder, s_Jni.NotificationManager.KEY_SMALL_ICON, notification.SmallIcon);
886916
if (!string.IsNullOrEmpty(notification.LargeIcon))
887917
s_Jni.NotificationManager.SetNotificationIcon(notificationBuilder, s_Jni.NotificationManager.KEY_LARGE_ICON, notification.LargeIcon);
@@ -919,17 +949,12 @@ public static AndroidJavaObject CreateNotificationBuilder(int id, AndroidNotific
919949
if (notification.GroupAlertBehaviour != GroupAlertBehaviours.GroupAlertAll) // All is default value
920950
s_Jni.NotificationManager.SetNotificationGroupAlertBehavior(notificationBuilder, (int)notification.GroupAlertBehaviour);
921951

922-
using (var extras = s_Jni.NotificationBuilder.GetExtras(notificationBuilder))
923-
{
924-
s_Jni.Bundle.PutInt(extras, s_Jni.NotificationManager.KEY_ID, id);
925-
s_Jni.Bundle.PutLong(extras, s_Jni.NotificationManager.KEY_REPEAT_INTERVAL, notification.RepeatInterval.ToLong());
926-
s_Jni.Bundle.PutLong(extras, s_Jni.NotificationManager.KEY_FIRE_TIME, fireTime);
927-
s_Jni.Bundle.PutBoolean(extras, s_Jni.NotificationManager.KEY_SHOW_IN_FOREGROUND, notification.ShowInForeground);
928-
if (!string.IsNullOrEmpty(notification.IntentData))
929-
s_Jni.Bundle.PutString(extras, s_Jni.NotificationManager.KEY_INTENT_DATA, notification.IntentData);
930-
}
931-
932-
return notificationBuilder;
952+
extras = s_Jni.NotificationBuilder.GetExtras(notificationBuilder);
953+
s_Jni.Bundle.PutLong(extras, s_Jni.NotificationManager.KEY_REPEAT_INTERVAL, notification.RepeatInterval.ToLong());
954+
s_Jni.Bundle.PutLong(extras, s_Jni.NotificationManager.KEY_FIRE_TIME, fireTime);
955+
s_Jni.Bundle.PutBoolean(extras, s_Jni.NotificationManager.KEY_SHOW_IN_FOREGROUND, notification.ShowInForeground);
956+
if (!string.IsNullOrEmpty(notification.IntentData))
957+
s_Jni.Bundle.PutString(extras, s_Jni.NotificationManager.KEY_INTENT_DATA, notification.IntentData);
933958
}
934959

935960
internal static AndroidNotificationIntentData GetNotificationData(AndroidJavaObject notificationObj)

com.unity.mobile.notifications/Runtime/Android/Plugins/com/unity/androidnotifications/UnityNotificationBackgroundThread.java

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,54 @@
11
package com.unity.androidnotifications;
22

3+
import static com.unity.androidnotifications.UnityNotificationManager.KEY_ID;
34
import static com.unity.androidnotifications.UnityNotificationManager.TAG_UNITY;
45

56
import android.app.Notification;
67
import android.content.Context;
78
import android.util.Log;
89

10+
import java.util.concurrent.ConcurrentHashMap;
911
import java.util.concurrent.LinkedTransferQueue;
12+
import java.util.Enumeration;
1013
import java.util.HashSet;
14+
import java.util.List;
1115
import java.util.Set;
1216

1317
public class UnityNotificationBackgroundThread extends Thread {
1418
private static abstract class Task {
1519
// returns true if notificationIds was modified (needs to be saved)
16-
public abstract boolean run(Context context, Set<String> notificationIds);
20+
public abstract boolean run(Context context, ConcurrentHashMap<Integer, Notification.Builder> notifications);
1721
}
1822

1923
private static class ScheduleNotificationTask extends Task {
2024
private int notificationId;
2125
private Notification.Builder notificationBuilder;
26+
private boolean isNew;
2227

23-
public ScheduleNotificationTask(int id, Notification.Builder builder) {
28+
public ScheduleNotificationTask(int id, Notification.Builder builder, boolean addedNew) {
2429
notificationId = id;
2530
notificationBuilder = builder;
31+
isNew = addedNew;
2632
}
2733

2834
@Override
29-
public boolean run(Context context, Set<String> notificationIds) {
35+
public boolean run(Context context, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
3036
String id = String.valueOf(notificationId);
37+
Integer ID = Integer.valueOf(notificationId);
38+
boolean didSchedule = false;
3139
try {
3240
UnityNotificationManager.mUnityNotificationManager.performNotificationScheduling(notificationId, notificationBuilder);
33-
return notificationIds.add(id);
41+
didSchedule = true;
3442
} finally {
35-
// if failed to schedule or replace, remove from settings and cache, so the status is correctly reported
36-
if (!notificationIds.contains(id)) {
43+
// if failed to schedule or replace, remove
44+
if (!didSchedule) {
45+
notifications.remove(notificationId);
46+
UnityNotificationManager.cancelPendingNotificationIntent(context, notificationId);
3747
UnityNotificationManager.deleteExpiredNotificationIntent(context, id);
38-
UnityNotificationManager.removeScheduledNotification(Integer.valueOf(notificationId));
3948
}
4049
}
50+
51+
return isNew;
4152
}
4253
}
4354

@@ -49,11 +60,10 @@ public CancelNotificationTask(int id) {
4960
}
5061

5162
@Override
52-
public boolean run(Context context, Set<String> notificationIds) {
63+
public boolean run(Context context, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
5364
UnityNotificationManager.cancelPendingNotificationIntent(context, notificationId);
54-
String id = String.valueOf(notificationId);
55-
if (notificationIds.remove(id)) {
56-
UnityNotificationManager.deleteExpiredNotificationIntent(context, id);
65+
if (notifications.remove(notificationId) != null) {
66+
UnityNotificationManager.deleteExpiredNotificationIntent(context, String.valueOf(notificationId));
5767
return true;
5868
}
5969

@@ -63,16 +73,18 @@ public boolean run(Context context, Set<String> notificationIds) {
6373

6474
private static class CancelAllNotificationsTask extends Task {
6575
@Override
66-
public boolean run(Context context, Set<String> notificationIds) {
67-
if (notificationIds.isEmpty())
76+
public boolean run(Context context, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
77+
if (notifications.isEmpty())
6878
return false;
6979

70-
for (String id : notificationIds) {
71-
UnityNotificationManager.cancelPendingNotificationIntent(context, Integer.valueOf(id));
72-
UnityNotificationManager.deleteExpiredNotificationIntent(context, id);
80+
Enumeration<Integer> ids = notifications.keys();
81+
while (ids.hasMoreElements()) {
82+
Integer notificationId = ids.nextElement();
83+
UnityNotificationManager.cancelPendingNotificationIntent(context, notificationId);
84+
UnityNotificationManager.deleteExpiredNotificationIntent(context, String.valueOf(notificationId));
7385
}
7486

75-
notificationIds.clear();
87+
notifications.clear();
7688
return true;
7789
}
7890
}
@@ -85,22 +97,33 @@ public HousekeepingTask(UnityNotificationBackgroundThread th) {
8597
}
8698

8799
@Override
88-
public boolean run(Context context, Set<String> notificationIds) {
100+
public boolean run(Context context, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
101+
HashSet<String> notificationIds = new HashSet<>();
102+
Enumeration<Integer> ids = notifications.keys();
103+
while (ids.hasMoreElements()) {
104+
notificationIds.add(String.valueOf(ids.nextElement()));
105+
}
89106
thread.performHousekeeping(context, notificationIds);
90107
return false;
91108
}
92109
}
93110

94111
private static final int TASKS_FOR_HOUSEKEEPING = 50;
95112
private LinkedTransferQueue<Task> mTasks = new LinkedTransferQueue();
113+
private ConcurrentHashMap<Integer, Notification.Builder> mScheduledNotifications;
114+
private static Context mContext;
96115
private int mTasksSinceHousekeeping = TASKS_FOR_HOUSEKEEPING; // we want hoursekeeping at the start
97116

98-
public UnityNotificationBackgroundThread() {
99-
enqueueHousekeeping();
117+
public UnityNotificationBackgroundThread(Context context, ConcurrentHashMap<Integer, Notification.Builder> scheduledNotifications) {
118+
mContext = context;
119+
mScheduledNotifications = scheduledNotifications;
120+
// rescheduling after reboot may have loaded, otherwise load here
121+
if (mScheduledNotifications.size() == 0)
122+
loadNotifications();
100123
}
101124

102-
public void enqueueNotification(int id, Notification.Builder notificationBuilder) {
103-
mTasks.add(new UnityNotificationBackgroundThread.ScheduleNotificationTask(id, notificationBuilder));
125+
public void enqueueNotification(int id, Notification.Builder notificationBuilder, boolean addedNew) {
126+
mTasks.add(new UnityNotificationBackgroundThread.ScheduleNotificationTask(id, notificationBuilder, addedNew));
104127
}
105128

106129
public void enqueueCancelNotification(int id) {
@@ -117,13 +140,11 @@ private void enqueueHousekeeping() {
117140

118141
@Override
119142
public void run() {
120-
Context context = UnityNotificationManager.mUnityNotificationManager.mContext;
121-
HashSet<String> notificationIds = new HashSet(UnityNotificationManager.getScheduledNotificationIDs(context));
122143
boolean haveChanges = false;
123144
while (true) {
124145
try {
125146
Task task = mTasks.take();
126-
haveChanges |= executeTask(context, task, notificationIds);
147+
haveChanges |= executeTask(mContext, task, mScheduledNotifications);
127148
if (!(task instanceof HousekeepingTask))
128149
++mTasksSinceHousekeeping;
129150
if (mTasks.size() == 0 && haveChanges) {
@@ -137,9 +158,9 @@ public void run() {
137158
}
138159
}
139160

140-
private boolean executeTask(Context context, Task task, Set<String> notificationIds) {
161+
private boolean executeTask(Context context, Task task, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
141162
try {
142-
return task.run(context, notificationIds);
163+
return task.run(context, notifications);
143164
} catch (Exception e) {
144165
Log.e(TAG_UNITY, "Exception executing notification task", e);
145166
return false;
@@ -154,4 +175,12 @@ private void performHousekeeping(Context context, Set<String> notificationIds) {
154175
UnityNotificationManager.performNotificationHousekeeping(context, notificationIds);
155176
UnityNotificationManager.saveScheduledNotificationIDs(context, notificationIds);
156177
}
178+
179+
private void loadNotifications() {
180+
List<Notification.Builder> notifications = UnityNotificationManager.loadSavedNotifications(mContext);
181+
for (Notification.Builder builder : notifications) {
182+
int id = builder.getExtras().getInt(KEY_ID, -1);
183+
mScheduledNotifications.put(id, builder);
184+
}
185+
}
157186
}

0 commit comments

Comments
 (0)