Skip to content

Commit 68af9fb

Browse files
authored
Merge pull request #195 from Unity-Technologies/android/refactor-threading
Android: refactor threading and notification scheduling
2 parents 3f1de8f + 90b1560 commit 68af9fb

File tree

5 files changed

+294
-125
lines changed

5 files changed

+294
-125
lines changed

TestProjects/com.unity.mobile-notifications-sample/Assets/Scripts/AndroidTest.cs

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class AndroidTest : MonoBehaviour
2323
private Button ButtonModifyExplicitID;
2424
private Button ButtonCancelExplicitID;
2525
private Button ButtonCheckStatusExplicitID;
26+
private AndroidNotificationTemplate[] AndroidNotificationsTemplates;
2627

2728
public int notificationExplicitID
2829
{
@@ -107,6 +108,32 @@ private void HandleLastNotificationIntent()
107108
}
108109
}
109110

111+
private AndroidNotification parseNotificationTemplate(AndroidNotificationTemplate template)
112+
{
113+
AndroidNotification newNotification = new AndroidNotification()
114+
{
115+
Title = template.Title,
116+
Text = template.Text,
117+
118+
SmallIcon = template.SmallIcon,
119+
LargeIcon = template.LargeIcon,
120+
Style = template.NotificationStyle,
121+
FireTime = System.DateTime.Now.AddSeconds(template.FireInSeconds),
122+
Color = template.Color,
123+
Number = template.Number,
124+
ShouldAutoCancel = template.ShouldAutoCancel,
125+
UsesStopwatch = template.UsesStopWatch,
126+
Group = template.Group,
127+
GroupSummary = template.GroupSummary,
128+
SortKey = template.SortKey,
129+
IntentData = template.IntentData,
130+
ShowTimestamp = template.ShowTimestamp,
131+
RepeatInterval = TimeSpan.FromSeconds(template.RepeatInterval),
132+
//ShowInForeground = template.ShowInForeground
133+
};
134+
return newNotification;
135+
}
136+
110137
private void InstantiateAllTestButtons()
111138
{
112139
m_groups = new Dictionary<string, OrderedDictionary>();
@@ -123,40 +150,16 @@ private void InstantiateAllTestButtons()
123150
m_groups["Modify"]["Modify pending Explicit notification"] = new Action(() => { ModifyExplicitNotification(); });
124151
m_groups["Modify"]["Cancel pending Explicit notification"] = new Action(() => { CancelExplicitNotification(); });
125152
m_groups["Modify"]["Check status of Explicit notification"] = new Action(() => { CheckStatusOfExplicitNotification (); });
126-
127-
153+
AndroidNotificationsTemplates = Resources.LoadAll<AndroidNotificationTemplate>("AndroidNotifications");
128154
m_groups["Send"] = new OrderedDictionary();
129-
foreach (AndroidNotificationTemplate template in Resources.LoadAll("AndroidNotifications", typeof(AndroidNotificationTemplate)))
155+
foreach (AndroidNotificationTemplate template in AndroidNotificationsTemplates)
130156
{
131157
if (template == null) continue;
132-
m_groups["Send"][$"[{template.FireInSeconds}s] {template.ButtonName}"] = new Action(() => {
133-
SendNotification(
134-
new AndroidNotification()
135-
{
136-
Title = template.Title,
137-
Text = template.Text,
138-
139-
SmallIcon = template.SmallIcon,
140-
LargeIcon = template.LargeIcon,
141-
Style = template.NotificationStyle,
142-
FireTime = System.DateTime.Now.AddSeconds(template.FireInSeconds),
143-
Color = template.Color,
144-
Number = template.Number,
145-
ShouldAutoCancel = template.ShouldAutoCancel,
146-
UsesStopwatch = template.UsesStopWatch,
147-
Group = template.Group,
148-
GroupSummary = template.GroupSummary,
149-
SortKey = template.SortKey,
150-
IntentData = template.IntentData,
151-
ShowTimestamp = template.ShowTimestamp,
152-
RepeatInterval = TimeSpan.FromSeconds(template.RepeatInterval),
153-
ShowInForeground = template.ShowInForeground,
154-
},
155-
template.Channel, template.NotificationID
156-
);
158+
m_groups["Send"][$"[{template.FireInSeconds}s] {template.ButtonName}"] = new Action(() =>
159+
{
160+
SendNotification(parseNotificationTemplate(template), template.Channel, template.NotificationID);
157161
});
158162
}
159-
160163
m_groups["Cancellation"] = new OrderedDictionary();
161164
m_groups["Cancellation"]["Cancel all notifications"] = new Action(() => { CancelAllNotifications(); });
162165
m_groups["Cancellation"]["Cancel pending notifications"] = new Action(() => { CancelPendingNotifications(); });
@@ -209,7 +212,8 @@ private void InstantiateAllTestButtons()
209212
AndroidNotificationCenter.OpenNotificationSettings("secondary_channel");
210213
});
211214
m_groups["Channels"]["Delete All Channels"] = new Action(() => { DeleteAllChannels(); });
212-
215+
m_groups["Custom sequences"] = new OrderedDictionary();
216+
m_groups["Custom sequences"]["Run custom sequence"] = new Action(() => { StartCoroutine(RunCustomSequence()); });
213217
foreach (KeyValuePair<string, OrderedDictionary> group in m_groups)
214218
{
215219
// Instantiate group
@@ -288,6 +292,7 @@ public void SendNotification(AndroidNotification notification, string channel =
288292
}
289293
if (notificationID != 0)
290294
{
295+
notification.Text = "ID: " + notificationID + " " + notification.Text;
291296
AndroidNotificationCenter.SendNotificationWithExplicitID(notification, channel, notificationID);
292297
notificationExplicitID = notificationID;
293298
}
@@ -354,6 +359,25 @@ public void ScrollLogsToBottom()
354359
m_gameObjectReferences.LogsScrollRect.verticalNormalizedPosition = 0f;
355360
}
356361

362+
IEnumerator RunCustomSequence()
363+
{
364+
AndroidNotificationTemplate ant = AndroidNotificationsTemplates[4];
365+
SendNotification(parseNotificationTemplate(ant), ant.Channel, 15);
366+
SendNotification(parseNotificationTemplate(ant), ant.Channel, 20);
367+
SendNotification(parseNotificationTemplate(ant), ant.Channel, 28);
368+
SendNotification(parseNotificationTemplate(ant), ant.Channel, 14);
369+
yield return new WaitForSeconds(ant.FireInSeconds+5);
370+
AndroidNotificationCenter.CancelNotification(15);
371+
yield return new WaitForSeconds(5);
372+
AndroidNotificationCenter.CancelDisplayedNotification(20);
373+
yield return new WaitForSeconds(5);
374+
SendNotification(parseNotificationTemplate(ant), ant.Channel, 99);
375+
SendNotification(parseNotificationTemplate(ant), ant.Channel, 99);
376+
yield return new WaitForSeconds(ant.FireInSeconds-1);
377+
SendNotification(parseNotificationTemplate(ant), ant.Channel, 99);
378+
SendNotification(parseNotificationTemplate(ant), ant.Channel, 99);
379+
}
380+
357381
#endif
358382
}
359383
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package com.unity.androidnotifications;
2+
3+
import static com.unity.androidnotifications.UnityNotificationManager.TAG_UNITY;
4+
5+
import android.app.Notification;
6+
import android.content.Context;
7+
import android.util.Log;
8+
9+
import java.util.concurrent.LinkedTransferQueue;
10+
import java.util.HashSet;
11+
import java.util.Set;
12+
13+
public class UnityNotificationBackgroundThread extends Thread {
14+
private static abstract class Task {
15+
// returns true if notificationIds was modified (needs to be saved)
16+
public abstract boolean run(Context context, Set<String> notificationIds);
17+
}
18+
19+
private static class ScheduleNotificationTask extends Task {
20+
private int notificationId;
21+
private Notification.Builder notificationBuilder;
22+
23+
public ScheduleNotificationTask(int id, Notification.Builder builder) {
24+
notificationId = id;
25+
notificationBuilder = builder;
26+
}
27+
28+
@Override
29+
public boolean run(Context context, Set<String> notificationIds) {
30+
String id = String.valueOf(notificationId);
31+
try {
32+
UnityNotificationManager.mUnityNotificationManager.performNotificationScheduling(notificationId, notificationBuilder);
33+
return notificationIds.add(id);
34+
} finally {
35+
// if failed to schedule or replace, remove from settings and cache, so the status is correctly reported
36+
if (!notificationIds.contains(id)) {
37+
UnityNotificationManager.deleteExpiredNotificationIntent(context, id);
38+
UnityNotificationManager.removeScheduledNotification(Integer.valueOf(notificationId));
39+
}
40+
}
41+
}
42+
}
43+
44+
private static class CancelNotificationTask extends Task {
45+
private int notificationId;
46+
47+
public CancelNotificationTask(int id) {
48+
notificationId = id;
49+
}
50+
51+
@Override
52+
public boolean run(Context context, Set<String> notificationIds) {
53+
UnityNotificationManager.cancelPendingNotificationIntent(context, notificationId);
54+
String id = String.valueOf(notificationId);
55+
if (notificationIds.remove(id)) {
56+
UnityNotificationManager.deleteExpiredNotificationIntent(context, id);
57+
return true;
58+
}
59+
60+
return false;
61+
}
62+
}
63+
64+
private static class CancelAllNotificationsTask extends Task {
65+
@Override
66+
public boolean run(Context context, Set<String> notificationIds) {
67+
if (notificationIds.isEmpty())
68+
return false;
69+
70+
for (String id : notificationIds) {
71+
UnityNotificationManager.cancelPendingNotificationIntent(context, Integer.valueOf(id));
72+
UnityNotificationManager.deleteExpiredNotificationIntent(context, id);
73+
}
74+
75+
notificationIds.clear();
76+
return true;
77+
}
78+
}
79+
80+
private static class HousekeepingTask extends Task {
81+
UnityNotificationBackgroundThread thread;
82+
83+
public HousekeepingTask(UnityNotificationBackgroundThread th) {
84+
thread = th;
85+
}
86+
87+
@Override
88+
public boolean run(Context context, Set<String> notificationIds) {
89+
thread.performHousekeeping(context, notificationIds);
90+
return false;
91+
}
92+
}
93+
94+
private static final int TASKS_FOR_HOUSEKEEPING = 50;
95+
private LinkedTransferQueue<Task> mTasks = new LinkedTransferQueue();
96+
private int mTasksSinceHousekeeping = TASKS_FOR_HOUSEKEEPING; // we want hoursekeeping at the start
97+
98+
public UnityNotificationBackgroundThread() {
99+
enqueueHousekeeping();
100+
}
101+
102+
public void enqueueNotification(int id, Notification.Builder notificationBuilder) {
103+
mTasks.add(new UnityNotificationBackgroundThread.ScheduleNotificationTask(id, notificationBuilder));
104+
}
105+
106+
public void enqueueCancelNotification(int id) {
107+
mTasks.add(new CancelNotificationTask(id));
108+
}
109+
110+
public void enqueueCancelAllNotifications() {
111+
mTasks.add(new CancelAllNotificationsTask());
112+
}
113+
114+
private void enqueueHousekeeping() {
115+
mTasks.add(new HousekeepingTask(this));
116+
}
117+
118+
@Override
119+
public void run() {
120+
Context context = UnityNotificationManager.mUnityNotificationManager.mContext;
121+
HashSet<String> notificationIds = new HashSet(UnityNotificationManager.getScheduledNotificationIDs(context));
122+
boolean haveChanges = false;
123+
while (true) {
124+
try {
125+
Task task = mTasks.take();
126+
haveChanges |= executeTask(context, task, notificationIds);
127+
if (!(task instanceof HousekeepingTask))
128+
++mTasksSinceHousekeeping;
129+
if (mTasks.size() == 0 && haveChanges) {
130+
haveChanges = false;
131+
enqueueHousekeeping();
132+
}
133+
} catch (InterruptedException e) {
134+
if (mTasks.isEmpty())
135+
break;
136+
}
137+
}
138+
}
139+
140+
private boolean executeTask(Context context, Task task, Set<String> notificationIds) {
141+
try {
142+
return task.run(context, notificationIds);
143+
} catch (Exception e) {
144+
Log.e(TAG_UNITY, "Exception executing notification task", e);
145+
return false;
146+
}
147+
}
148+
149+
private void performHousekeeping(Context context, Set<String> notificationIds) {
150+
// don't do housekeeping if last task we did was housekeeping (other=1)
151+
boolean performHousekeeping = mTasksSinceHousekeeping >= TASKS_FOR_HOUSEKEEPING;
152+
mTasksSinceHousekeeping = 0;
153+
if (performHousekeeping)
154+
UnityNotificationManager.performNotificationHousekeeping(context, notificationIds);
155+
UnityNotificationManager.saveScheduledNotificationIDs(context, notificationIds);
156+
}
157+
}

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

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)