Skip to content

Commit f68953f

Browse files
authored
Merge pull request #220 from Unity-Technologies/android-exact-scheduling
Android 12+ exact scheduling
2 parents 555fc56 + 76d748f commit f68953f

File tree

10 files changed

+225
-88
lines changed

10 files changed

+225
-88
lines changed

TestProjects/Main/ProjectSettings/NotificationsSettings.asset

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@
2929
"m_Keys": [
3030
"UnityNotificationAndroidRescheduleOnDeviceRestart",
3131
"UnityNotificationAndroidUseCustomActivity",
32-
"UnityNotificationAndroidCustomActivityString"
32+
"UnityNotificationAndroidCustomActivityString",
33+
"UnityNotificationAndroidScheduleExactAlarms"
3334
],
3435
"m_Values": [
3536
"True",
3637
"False",
37-
"com.unity3d.player.UnityPlayerActivity"
38+
"com.unity3d.player.UnityPlayerActivity",
39+
"3"
3840
]
3941
},
4042
"DrawableResources": [
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
3+
namespace Unity.Notifications
4+
{
5+
/// <summary>
6+
/// Whether to schedule notifications at exact time or approximately (saves power).
7+
/// Exact scheduling is available in Android 6 (API 23) and newer, lower versions always use inexact scheduling.
8+
/// Android 12 (API 31) or newer requires SCHEDULE_EXACT_ALARM permission and grant from user to use exact scheduling.
9+
/// Android 13 (API 33) or newer can use USE_EXACT_ALARM permission to use exactscheduling without requesting users grant.
10+
/// </summary>
11+
[Flags]
12+
public enum AndroidExactSchedulingOption
13+
{
14+
/// <summary>
15+
/// Use exact scheduling when possible.
16+
/// </summary>
17+
ExactWhenAvailable = 1,
18+
19+
/// <summary>
20+
/// Add SCHEDULE_EXACT_ALARM permission to the manifest.
21+
/// </summary>
22+
AddScheduleExactPermission = 1 << 1,
23+
24+
/// <summary>
25+
/// Add USE_EXACT_ALARM permission to the manifest.
26+
/// </summary>
27+
AddUseExactAlarmPermission = 1 << 2,
28+
}
29+
}

com.unity.mobile.notifications/Editor/AndroidExactSchedulingOption.cs.meta

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

com.unity.mobile.notifications/Editor/AndroidNotificationPostProcessor.cs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#if UNITY_ANDROID
22
using System;
3+
using System.Collections.Generic;
34
using System.IO;
45
using System.Xml;
56
using UnityEditor;
@@ -68,14 +69,14 @@ private void InjectAndroidManifest(string projectPath)
6869

6970
var settings = NotificationSettingsManager.Initialize().AndroidNotificationSettingsFlat;
7071

71-
var useCustomActivity = (bool)settings.Find(i => i.Key == "UnityNotificationAndroidUseCustomActivity").Value;
72+
var useCustomActivity = GetSetting<bool>(settings, NotificationSettings.AndroidSettings.USE_CUSTOM_ACTIVITY);
7273
if (useCustomActivity)
7374
{
74-
var customActivity = (string)settings.Find(i => i.Key == "UnityNotificationAndroidCustomActivityString").Value;
75+
var customActivity = GetSetting<string>(settings, NotificationSettings.AndroidSettings.CUSTOM_ACTIVITY_CLASS);
7576
AppendAndroidMetadataField(manifestPath, manifestDoc, "custom_notification_android_activity", customActivity);
7677
}
7778

78-
var enableRescheduleOnRestart = (bool)settings.Find(i => i.Key == "UnityNotificationAndroidRescheduleOnDeviceRestart").Value;
79+
var enableRescheduleOnRestart = GetSetting<bool>(settings, NotificationSettings.AndroidSettings.RESCHEDULE_ON_RESTART);
7980
if (enableRescheduleOnRestart)
8081
{
8182
AppendAndroidMetadataField(manifestPath, manifestDoc, "reschedule_notifications_on_restart", "true");
@@ -84,9 +85,29 @@ private void InjectAndroidManifest(string projectPath)
8485

8586
AppendAndroidPermissionField(manifestPath, manifestDoc, "android.permission.POST_NOTIFICATIONS");
8687

88+
var exactScheduling = GetSetting<AndroidExactSchedulingOption>(settings, NotificationSettings.AndroidSettings.EXACT_ALARM);
89+
bool enableExact = (exactScheduling & AndroidExactSchedulingOption.ExactWhenAvailable) != 0;
90+
AppendAndroidMetadataField(manifestPath, manifestDoc, "com.unity.androidnotifications.exact_scheduling", enableExact ? "1" : "0");
91+
if (enableExact)
92+
{
93+
bool scheduleExact = (exactScheduling & AndroidExactSchedulingOption.AddScheduleExactPermission) != 0;
94+
bool useExact = (exactScheduling & AndroidExactSchedulingOption.AddUseExactAlarmPermission) != 0;
95+
// as documented here: https://developer.android.com/reference/android/Manifest.permission#USE_EXACT_ALARM
96+
// only one of these two attributes should be used or max sdk set so on any device it's one or the other
97+
if (scheduleExact)
98+
AppendAndroidPermissionField(manifestPath, manifestDoc, "android.permission.SCHEDULE_EXACT_ALARM", useExact ? "32" : null);
99+
if (useExact)
100+
AppendAndroidPermissionField(manifestPath, manifestDoc, "android.permission.USE_EXACT_ALARM");
101+
}
102+
87103
manifestDoc.Save(manifestPath);
88104
}
89105

106+
private static T GetSetting<T>(List<NotificationSetting> settings, string key)
107+
{
108+
return (T)settings.Find(i => i.Key == key).Value;
109+
}
110+
90111
internal static void InjectReceivers(string manifestPath, XmlDocument manifestXmlDoc)
91112
{
92113
const string kNotificationManagerName = "com.unity.androidnotifications.UnityNotificationManager";
@@ -149,24 +170,38 @@ internal static void InjectReceivers(string manifestPath, XmlDocument manifestXm
149170
notificationRestartOnBootReceiver.SetAttribute("exported", kAndroidNamespaceURI, "false");
150171
}
151172

152-
internal static void AppendAndroidPermissionField(string manifestPath, XmlDocument xmlDoc, string name)
173+
internal static void AppendAndroidPermissionField(string manifestPath, XmlDocument xmlDoc, string name, string maxSdk = null)
153174
{
154175
var manifestNode = xmlDoc.SelectSingleNode("manifest");
155176
if (manifestNode == null)
156177
throw new ArgumentException(string.Format("Missing 'manifest' node in '{0}'.", manifestPath));
157178

179+
XmlElement metaDataNode = null;
158180
foreach (XmlNode node in manifestNode.ChildNodes)
159181
{
160182
if (!(node is XmlElement) || node.Name != "uses-permission")
161183
continue;
162184

163-
var elementName = ((XmlElement)node).GetAttribute("name", kAndroidNamespaceURI);
185+
var element = (XmlElement)node;
186+
var elementName = element.GetAttribute("name", kAndroidNamespaceURI);
164187
if (elementName == name)
165-
return;
188+
{
189+
if (maxSdk == null)
190+
return;
191+
var maxSdkAttr = element.GetAttribute("maxSdkVersion", kAndroidNamespaceURI);
192+
if (!string.IsNullOrEmpty(maxSdkAttr))
193+
return;
194+
metaDataNode = element;
195+
}
166196
}
167197

168-
XmlElement metaDataNode = xmlDoc.CreateElement("uses-permission");
169-
metaDataNode.SetAttribute("name", kAndroidNamespaceURI, name);
198+
if (metaDataNode == null)
199+
{
200+
metaDataNode = xmlDoc.CreateElement("uses-permission");
201+
metaDataNode.SetAttribute("name", kAndroidNamespaceURI, name);
202+
}
203+
if (maxSdk != null)
204+
metaDataNode.SetAttribute("maxSdkVersion", kAndroidNamespaceURI, maxSdk);
170205

171206
manifestNode.AppendChild(metaDataNode);
172207
}

com.unity.mobile.notifications/Editor/NotificationSettings.cs

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,23 @@ private static T GetSettingValue<T>(BuildTargetGroup target, string key)
4949
/// </summary>
5050
public static class AndroidSettings
5151
{
52+
internal static readonly string RESCHEDULE_ON_RESTART = "UnityNotificationAndroidRescheduleOnDeviceRestart";
53+
internal static readonly string EXACT_ALARM = "UnityNotificationAndroidScheduleExactAlarms";
54+
internal static readonly string USE_CUSTOM_ACTIVITY = "UnityNotificationAndroidUseCustomActivity";
55+
internal static readonly string CUSTOM_ACTIVITY_CLASS = "UnityNotificationAndroidCustomActivityString";
56+
5257
/// <summary>
5358
/// By default AndroidSettings removes all scheduled notifications when the device is restarted. Enable this to automatically reschedule all non expired notifications when the device is turned back on.
5459
/// </summary>
5560
public static bool RescheduleOnDeviceRestart
5661
{
5762
get
5863
{
59-
return GetSettingValue<bool>(BuildTargetGroup.Android, "UnityNotificationAndroidRescheduleOnDeviceRestart");
64+
return GetSettingValue<bool>(BuildTargetGroup.Android, RESCHEDULE_ON_RESTART);
6065
}
6166
set
6267
{
63-
SetSettingValue<bool>(BuildTargetGroup.Android, "UnityNotificationAndroidRescheduleOnDeviceRestart", value);
68+
SetSettingValue<bool>(BuildTargetGroup.Android, RESCHEDULE_ON_RESTART, value);
6469
}
6570
}
6671

@@ -71,11 +76,11 @@ public static bool UseCustomActivity
7176
{
7277
get
7378
{
74-
return GetSettingValue<bool>(BuildTargetGroup.Android, "UnityNotificationAndroidUseCustomActivity");
79+
return GetSettingValue<bool>(BuildTargetGroup.Android, USE_CUSTOM_ACTIVITY);
7580
}
7681
set
7782
{
78-
SetSettingValue<bool>(BuildTargetGroup.Android, "UnityNotificationAndroidUseCustomActivity", value);
83+
SetSettingValue<bool>(BuildTargetGroup.Android, USE_CUSTOM_ACTIVITY, value);
7984
}
8085
}
8186

@@ -86,11 +91,26 @@ public static string CustomActivityString
8691
{
8792
get
8893
{
89-
return GetSettingValue<string>(BuildTargetGroup.Android, "UnityNotificationAndroidCustomActivityString");
94+
return GetSettingValue<string>(BuildTargetGroup.Android, CUSTOM_ACTIVITY_CLASS);
9095
}
9196
set
9297
{
93-
SetSettingValue<string>(BuildTargetGroup.Android, "UnityNotificationAndroidCustomActivityString", value);
98+
SetSettingValue<string>(BuildTargetGroup.Android, CUSTOM_ACTIVITY_CLASS, value);
99+
}
100+
}
101+
102+
/// <summary>
103+
/// A set of flags indicating whether to use exact scheduling and add supporting permissions.
104+
/// </summary>
105+
public static AndroidExactSchedulingOption ExactSchedulingOption
106+
{
107+
get
108+
{
109+
return GetSettingValue<AndroidExactSchedulingOption>(BuildTargetGroup.Android, EXACT_ALARM);
110+
}
111+
set
112+
{
113+
SetSettingValue<AndroidExactSchedulingOption>(BuildTargetGroup.Android, EXACT_ALARM, value);
94114
}
95115
}
96116

@@ -155,18 +175,26 @@ public static void ClearDrawableResources()
155175
/// </summary>
156176
public static class iOSSettings
157177
{
178+
internal static readonly string REQUEST_AUTH_ON_LAUNCH = "UnityNotificationRequestAuthorizationOnAppLaunch";
179+
internal static readonly string DEFAULT_AUTH_OPTS = "UnityNotificationDefaultAuthorizationOptions";
180+
internal static readonly string ADD_PUSH_CAPABILITY = "UnityAddRemoteNotificationCapability";
181+
internal static readonly string REQUEST_PUSH_AUTH_ON_LAUNCH = "UnityNotificationRequestAuthorizationForRemoteNotificationsOnAppLaunch";
182+
internal static readonly string PUSH_NOTIFICATION_PRESENTATION = "UnityRemoteNotificationForegroundPresentationOptions";
183+
internal static readonly string USE_APS_RELEASE = "UnityUseAPSReleaseEnvironment";
184+
internal static readonly string USE_LOCATION_TRIGGER = "UnityUseLocationNotificationTrigger";
185+
158186
/// <summary>
159187
/// It's recommended to make the authorization request during the app's launch cycle. If this is enabled the user will be shown the authorization pop-up immediately when the app launches. If it’s unchecked you’ll need to manually create an AuthorizationRequest before your app can send or receive notifications.
160188
/// </summary>
161189
public static bool RequestAuthorizationOnAppLaunch
162190
{
163191
get
164192
{
165-
return GetSettingValue<bool>(BuildTargetGroup.iOS, "UnityNotificationRequestAuthorizationOnAppLaunch");
193+
return GetSettingValue<bool>(BuildTargetGroup.iOS, REQUEST_AUTH_ON_LAUNCH);
166194
}
167195
set
168196
{
169-
SetSettingValue<bool>(BuildTargetGroup.iOS, "UnityNotificationRequestAuthorizationOnAppLaunch", value);
197+
SetSettingValue<bool>(BuildTargetGroup.iOS, REQUEST_AUTH_ON_LAUNCH, value);
170198
}
171199
}
172200

@@ -177,11 +205,11 @@ public static AuthorizationOption DefaultAuthorizationOptions
177205
{
178206
get
179207
{
180-
return GetSettingValue<AuthorizationOption>(BuildTargetGroup.iOS, "UnityNotificationDefaultAuthorizationOptions");
208+
return GetSettingValue<AuthorizationOption>(BuildTargetGroup.iOS, DEFAULT_AUTH_OPTS);
181209
}
182210
set
183211
{
184-
SetSettingValue<AuthorizationOption>(BuildTargetGroup.iOS, "UnityNotificationDefaultAuthorizationOptions", value);
212+
SetSettingValue<AuthorizationOption>(BuildTargetGroup.iOS, DEFAULT_AUTH_OPTS, value);
185213
}
186214
}
187215

@@ -192,11 +220,11 @@ public static bool AddRemoteNotificationCapability
192220
{
193221
get
194222
{
195-
return GetSettingValue<bool>(BuildTargetGroup.iOS, "UnityAddRemoteNotificationCapability");
223+
return GetSettingValue<bool>(BuildTargetGroup.iOS, ADD_PUSH_CAPABILITY);
196224
}
197225
set
198226
{
199-
SetSettingValue<bool>(BuildTargetGroup.iOS, "UnityAddRemoteNotificationCapability", value);
227+
SetSettingValue<bool>(BuildTargetGroup.iOS, ADD_PUSH_CAPABILITY, value);
200228
}
201229
}
202230

@@ -207,11 +235,11 @@ public static bool NotificationRequestAuthorizationForRemoteNotificationsOnAppLa
207235
{
208236
get
209237
{
210-
return GetSettingValue<bool>(BuildTargetGroup.iOS, "UnityNotificationRequestAuthorizationForRemoteNotificationsOnAppLaunch");
238+
return GetSettingValue<bool>(BuildTargetGroup.iOS, REQUEST_PUSH_AUTH_ON_LAUNCH);
211239
}
212240
set
213241
{
214-
SetSettingValue<bool>(BuildTargetGroup.iOS, "UnityNotificationRequestAuthorizationForRemoteNotificationsOnAppLaunch", value);
242+
SetSettingValue<bool>(BuildTargetGroup.iOS, REQUEST_PUSH_AUTH_ON_LAUNCH, value);
215243
}
216244
}
217245

@@ -222,11 +250,11 @@ public static PresentationOption RemoteNotificationForegroundPresentationOptions
222250
{
223251
get
224252
{
225-
return GetSettingValue<PresentationOption>(BuildTargetGroup.iOS, "UnityRemoteNotificationForegroundPresentationOptions");
253+
return GetSettingValue<PresentationOption>(BuildTargetGroup.iOS, PUSH_NOTIFICATION_PRESENTATION);
226254
}
227255
set
228256
{
229-
SetSettingValue<PresentationOption>(BuildTargetGroup.iOS, "UnityRemoteNotificationForegroundPresentationOptions", value);
257+
SetSettingValue<PresentationOption>(BuildTargetGroup.iOS, PUSH_NOTIFICATION_PRESENTATION, value);
230258
}
231259
}
232260

@@ -237,11 +265,11 @@ public static bool UseAPSReleaseEnvironment
237265
{
238266
get
239267
{
240-
return GetSettingValue<bool>(BuildTargetGroup.iOS, "UnityUseAPSReleaseEnvironment");
268+
return GetSettingValue<bool>(BuildTargetGroup.iOS, USE_APS_RELEASE);
241269
}
242270
set
243271
{
244-
SetSettingValue<bool>(BuildTargetGroup.iOS, "UnityUseAPSReleaseEnvironment", value);
272+
SetSettingValue<bool>(BuildTargetGroup.iOS, USE_APS_RELEASE, value);
245273
}
246274
}
247275

@@ -252,11 +280,11 @@ public static bool UseLocationNotificationTrigger
252280
{
253281
get
254282
{
255-
return GetSettingValue<bool>(BuildTargetGroup.iOS, "UnityUseLocationNotificationTrigger");
283+
return GetSettingValue<bool>(BuildTargetGroup.iOS, USE_LOCATION_TRIGGER);
256284
}
257285
set
258286
{
259-
SetSettingValue<bool>(BuildTargetGroup.iOS, "UnityUseLocationNotificationTrigger", value);
287+
SetSettingValue<bool>(BuildTargetGroup.iOS, USE_LOCATION_TRIGGER, value);
260288
}
261289
}
262290
}

0 commit comments

Comments
 (0)