fix:1、sdk更换。2、修复bug
This commit is contained in:
+320
@@ -0,0 +1,320 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Notifications.Android
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows applying a rich notification style to a notification.
|
||||
/// </summary>
|
||||
public enum NotificationStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the default style.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Generate a large-format notification centered around an image.
|
||||
/// </summary>
|
||||
BigPictureStyle = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Generate a large-format notification that includes a lot of text.
|
||||
/// </summary>
|
||||
BigTextStyle = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows applying an alert behaviour to grouped notifications.
|
||||
/// </summary>
|
||||
public enum GroupAlertBehaviours
|
||||
{
|
||||
/// <summary>
|
||||
/// All notifications in a group with sound or vibration will make sound or vibrate, so this notification will not be muted when it is in a group.
|
||||
/// </summary>
|
||||
GroupAlertAll = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The summary notification in a group will be silenced (no sound or vibration) even if they would otherwise make sound or vibrate.
|
||||
/// Use this to mute this notification if this notification is a group summary.
|
||||
/// </summary>
|
||||
GroupAlertSummary = 1,
|
||||
|
||||
/// <summary>
|
||||
/// All children notification in a group will be silenced (no sound or vibration) even if they would otherwise make sound or vibrate.
|
||||
/// Use this to mute this notification if this notification is a group child. This must be set on all children notifications you want to mute.
|
||||
/// </summary>
|
||||
GroupAlertChildren = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data for setting up the big picture style notification.
|
||||
/// Properties that are not available in devices API level are ignored. See Android documentation for availibility.
|
||||
/// <see href="https://developer.android.com/reference/android/app/Notification.BigPictureStyle"/>
|
||||
/// </summary>
|
||||
public struct BigPictureStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// The override for large icon (requirements are the same).
|
||||
/// </summary>
|
||||
/// <see cref="AndroidNotification.LargeIcon"/>
|
||||
public string LargeIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The picture to be displayed.
|
||||
/// Can be resource name (like icon), file path or an URI supported by Android.
|
||||
/// </summary>
|
||||
public string Picture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The content title to be displayed in the notification.
|
||||
/// </summary>
|
||||
public string ContentTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The content description to set.
|
||||
/// </summary>
|
||||
public string ContentDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The summary text to be shown.
|
||||
/// </summary>
|
||||
public string SummaryText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show big picture in place of large icon when collapsed.
|
||||
/// </summary>
|
||||
public bool ShowWhenCollapsed { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AndroidNotification is used schedule a local notification, which includes the content of the notification.
|
||||
/// </summary>
|
||||
public struct AndroidNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// Notification title.
|
||||
/// Set the first line of text in the notification.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Notification body.
|
||||
/// Set the second line of text in the notification.
|
||||
/// </summary>
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Notification small icon.
|
||||
/// It will be used to represent the notification in the status bar and content view (unless overridden there by a large icon)
|
||||
/// The icon has to be registered in Notification Settings or a PNG file has to be placed in the `res/drawable` folder of the Android library plugin
|
||||
/// and it's name has to be specified without the extension.
|
||||
/// Alternatively it can also be URI supported by the OS.
|
||||
/// </summary>
|
||||
public string SmallIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date and time when the notification should be delivered.
|
||||
/// </summary>
|
||||
public DateTime FireTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The notification will be be repeated on every specified time interval.
|
||||
/// Do not set for one time notifications.
|
||||
/// </summary>
|
||||
public TimeSpan? RepeatInterval
|
||||
{
|
||||
get { return m_RepeatInterval; }
|
||||
set { m_RepeatInterval = value.HasValue ? value.Value : (-1L).ToTimeSpan(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notification large icon.
|
||||
/// Add a large icon to the notification content view. This image will be shown on the left of the notification view in place of the small icon (which will be placed in a small badge atop the large icon).
|
||||
/// The icon has to be registered in Notification Settings or a PNG file has to be placed in the `res/drawable` folder of the Android library plugin
|
||||
/// and it's name has to be specified without the extension.
|
||||
/// Alternatively it can be a file path or system supported URI.
|
||||
/// </summary>
|
||||
public string LargeIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Apply a custom style to the notification.
|
||||
/// Currently only BigPicture and BigText styles are supported.
|
||||
/// </summary>
|
||||
public NotificationStyle Style { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Accent color to be applied by the standard style templates when presenting this notification.
|
||||
/// The template design constructs a colorful header image by overlaying the icon image (stenciled in white) atop a field of this color. Alpha components are ignored.
|
||||
/// </summary>
|
||||
public Color? Color
|
||||
{
|
||||
get { return m_Color; }
|
||||
set { m_Color = value.HasValue ? value.Value : new Color(0, 0, 0, 0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the number of items this notification represents.
|
||||
/// Is displayed as a badge count on the notification icon if the launcher supports this behavior.
|
||||
/// </summary>
|
||||
public int Number { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This notification will automatically be dismissed when the user touches it.
|
||||
/// By default this behavior is turned off.
|
||||
/// </summary>
|
||||
public bool ShouldAutoCancel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Show the notification time field as a stopwatch instead of a timestamp.
|
||||
/// </summary>
|
||||
public bool UsesStopwatch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///Set this property for the notification to be made part of a group of notifications sharing the same key.
|
||||
/// Grouped notifications may display in a cluster or stack on devices which support such rendering.
|
||||
/// Only available on Android 7.0 (API level 24) and above.
|
||||
/// </summary>
|
||||
public string Group { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set this notification to be the group summary for a group of notifications. Requires the 'Group' property to also be set.
|
||||
/// Grouped notifications may display in a cluster or stack on devices which support such rendering.
|
||||
/// Only available on Android 7.0 (API level 24) and above.
|
||||
/// </summary>
|
||||
public bool GroupSummary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the group alert behavior for this notification. Set this property to mute this notification if alerts for this notification's group should be handled by a different notification.
|
||||
/// This is only applicable for notifications that belong to a group. This must be set on all notifications you want to mute.
|
||||
/// Only available on Android 8.0 (API level 26) and above.
|
||||
/// </summary>
|
||||
public GroupAlertBehaviours GroupAlertBehaviour { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sort key will be used to order this notification among other notifications from the same package.
|
||||
/// Notifications will be sorted lexicographically using this value.
|
||||
/// </summary>
|
||||
public string SortKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Use this to save arbitrary string data related to the notification.
|
||||
/// </summary>
|
||||
public string IntentData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable it to show a timestamp on the notification when it's delivered, unless the "CustomTimestamp" property is set "FireTime" will be shown.
|
||||
/// </summary>
|
||||
public bool ShowTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set this to show custom date instead of the notification's "FireTime" as the notification's timestamp'.
|
||||
/// </summary>
|
||||
public DateTime CustomTimestamp
|
||||
{
|
||||
get { return m_CustomTimestamp; }
|
||||
set
|
||||
{
|
||||
ShowCustomTimestamp = true;
|
||||
m_CustomTimestamp = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set this notification to be shown when app is in the foreground (default: true).
|
||||
/// </summary>
|
||||
public bool ShowInForeground
|
||||
{
|
||||
get => !m_SilentInForeground;
|
||||
set => m_SilentInForeground = !value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The necessary properties for big picture style notification.
|
||||
/// For convenience, assigning this property will also set the Style property.
|
||||
/// </summary>
|
||||
public BigPictureStyle? BigPicture
|
||||
{
|
||||
get { return m_BigPictureStyle; }
|
||||
set
|
||||
{
|
||||
m_BigPictureStyle = value;
|
||||
if (m_BigPictureStyle.HasValue && Style == NotificationStyle.None)
|
||||
Style = NotificationStyle.BigPictureStyle;
|
||||
else if (!m_BigPictureStyle.HasValue && Style == NotificationStyle.BigPictureStyle)
|
||||
Style = NotificationStyle.None;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool ShowCustomTimestamp { get; set; }
|
||||
|
||||
private Color m_Color;
|
||||
private TimeSpan m_RepeatInterval;
|
||||
private DateTime m_CustomTimestamp;
|
||||
private bool m_SilentInForeground;
|
||||
private BigPictureStyle? m_BigPictureStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Create a notification struct with all optional fields set to default values.
|
||||
/// </summary>
|
||||
/// <param name="title">Notification title</param>
|
||||
/// <param name="text">Text to show on notification</param>
|
||||
/// <param name="fireTime">Date and time when to show, can be DateTime.Now to show right away</param>
|
||||
public AndroidNotification(string title, string text, DateTime fireTime)
|
||||
{
|
||||
Title = title;
|
||||
Text = text;
|
||||
FireTime = fireTime;
|
||||
|
||||
SmallIcon = string.Empty;
|
||||
ShouldAutoCancel = false;
|
||||
LargeIcon = string.Empty;
|
||||
Style = NotificationStyle.None;
|
||||
Number = -1;
|
||||
UsesStopwatch = false;
|
||||
IntentData = string.Empty;
|
||||
Group = string.Empty;
|
||||
GroupSummary = false;
|
||||
SortKey = string.Empty;
|
||||
GroupAlertBehaviour = GroupAlertBehaviours.GroupAlertAll;
|
||||
ShowTimestamp = false;
|
||||
ShowCustomTimestamp = false;
|
||||
m_BigPictureStyle = null;
|
||||
|
||||
m_RepeatInterval = (-1L).ToTimeSpan();
|
||||
m_Color = new Color(0, 0, 0, 0);
|
||||
m_CustomTimestamp = (-1L).ToDatetime();
|
||||
m_SilentInForeground = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a repeatable notification struct with all optional fields set to default values.
|
||||
/// </summary>
|
||||
/// <param name="title">Notification title</param>
|
||||
/// <param name="text">Text to show on notification</param>
|
||||
/// <param name="fireTime">Date and time when to show, can be DateTime.Now to show right away</param>
|
||||
/// <param name="repeatInterval">Makes notification repeatable with this time interval</param>
|
||||
/// <remarks>
|
||||
/// There is a minimum period of 1 minute for repeating notifications.
|
||||
/// </remarks>
|
||||
public AndroidNotification(string title, string text, DateTime fireTime, TimeSpan repeatInterval)
|
||||
: this(title, text, fireTime)
|
||||
{
|
||||
RepeatInterval = repeatInterval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a notification struct with a custom small icon and all optional fields set to default values.
|
||||
/// </summary>
|
||||
/// <param name="title">Notification title</param>
|
||||
/// <param name="text">Text to show on notification</param>
|
||||
/// <param name="fireTime">Date and time when to show, can be DateTime.Now to show right away</param>
|
||||
/// <param name="repeatInterval">Makes notification repeatable with this time interval</param>
|
||||
/// <param name="smallIcon">Name of the small icon to be shown on notification</param>
|
||||
public AndroidNotification(string title, string text, DateTime fireTime, TimeSpan repeatInterval, string smallIcon)
|
||||
: this(title, text, fireTime, repeatInterval)
|
||||
{
|
||||
SmallIcon = smallIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5920d00d82f60ad438e2d483048328d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Notifications.Android
|
||||
{
|
||||
class NotificationCallback : AndroidJavaProxy
|
||||
{
|
||||
public NotificationCallback() : base("com.unity.androidnotifications.NotificationCallback")
|
||||
{
|
||||
}
|
||||
|
||||
public override AndroidJavaObject Invoke(string methodName, AndroidJavaObject[] args)
|
||||
{
|
||||
if (methodName.Equals("onSentNotification", StringComparison.InvariantCulture) && args != null && args.Length == 1)
|
||||
{
|
||||
onSentNotification(args[0]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return base.Invoke(methodName, args);
|
||||
}
|
||||
|
||||
public void onSentNotification(AndroidJavaObject notification)
|
||||
{
|
||||
AndroidReceivedNotificationMainThreadDispatcher.GetInstance().EnqueueReceivedNotification(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df44dfe6f4b394ca988636822db28499
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+1300
File diff suppressed because it is too large
Load Diff
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 330d6c453457d452dba1912232a97e01
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Notifications.Android
|
||||
{
|
||||
/// <summary>
|
||||
/// The level of interruption of this notification channel.
|
||||
/// The importance of a notification is used to determine how much the notification should interrupt the user (visually and audibly).
|
||||
/// The higher the importance of a notification, the more interruptive the notification will be.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The exact behaviour of each importance level might vary depending on the device and OS version on devices running Android 7.1 or older.
|
||||
/// </remarks>
|
||||
public enum Importance
|
||||
{
|
||||
/// <summary>
|
||||
/// A notification with no importance: does not show in the shade.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Low importance, notification is shown everywhere, but is not intrusive.
|
||||
/// </summary>
|
||||
Low = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Default importance, notification is shown everywhere, makes noise, but does not intrude visually.
|
||||
/// </summary>
|
||||
Default = 3,
|
||||
|
||||
/// <summary>
|
||||
/// High importance, notification is shown everywhere, makes noise and is shown on the screen.
|
||||
/// </summary>
|
||||
High = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether notifications appear on the lock screen.
|
||||
/// </summary>
|
||||
public enum LockScreenVisibility
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not reveal any part of this notification on a secure lock screen.
|
||||
/// </summary>
|
||||
Secret = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Show this notification on all lock screens, but conceal sensitive or private information on secure lock screens.
|
||||
/// </summary>
|
||||
Private = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Show this notification in its entirety on the lock screen.
|
||||
/// </summary>
|
||||
Public = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The wrapper of the Android notification channel. Use this to group notifications by groups.
|
||||
/// </summary>
|
||||
public struct AndroidNotificationChannel
|
||||
{
|
||||
/// <summary>
|
||||
/// Notification channel identifier.
|
||||
/// Must be specified when scheduling notifications.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Notification channel name which is visible to users.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User visible description of the notification channel.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the registered channel group this channel belongs to.
|
||||
/// </summary>
|
||||
public string Group { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Importance level which is applied to all notifications sent to the channel.
|
||||
/// This can be changed by users in the settings app. Android uses importance to determine how much the notification should interrupt the user (visually and audibly).
|
||||
/// The higher the importance of a notification, the more interruptive the notification will be.
|
||||
/// The possible importance levels are the following:
|
||||
/// High: Makes a sound and appears as a heads-up notification.
|
||||
/// Default: Makes a sound.
|
||||
/// Low: No sound.
|
||||
/// None: No sound and does not appear in the status bar.
|
||||
/// </summary>
|
||||
public Importance Importance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not notifications posted to this channel can bypass the Do Not Disturb.
|
||||
/// This can be changed by users in the settings app.
|
||||
/// </summary>
|
||||
public bool CanBypassDnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether notifications posted to this channel can appear as badges in a Launcher application.
|
||||
/// </summary>
|
||||
public bool CanShowBadge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether notifications posted to this channel should display notification lights, on devices that support that feature.
|
||||
/// This can be changed by users in the settings app.
|
||||
/// </summary>/
|
||||
public bool EnableLights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether notification posted to this channel should vibrate.
|
||||
/// This can be changed by users in the settings app.
|
||||
/// </summary>
|
||||
public bool EnableVibration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the vibration pattern for notifications posted to this channel.
|
||||
/// </summary>
|
||||
public long[] VibrationPattern { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether or not notifications posted to this channel are shown on the lockscreen in full or redacted form.
|
||||
/// This can be changed by users in the settings app.
|
||||
/// </summary>
|
||||
public LockScreenVisibility LockScreenVisibility { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a channel is enabled in the Settings app. User can block notifications for the entire app or for individual channels. The Importance of a blocked channel is set to None.
|
||||
/// </summary>
|
||||
public bool Enabled
|
||||
{
|
||||
get { return Importance != Importance.None; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a notification channel struct with all optional fields set to default values.
|
||||
/// </summary>
|
||||
/// <param name="id">ID for the channel</param>
|
||||
/// <param name="name">Channel name</param>
|
||||
/// <param name="description">Channel description</param>
|
||||
/// <param name="importance">Importance of the channel</param>
|
||||
public AndroidNotificationChannel(string id, string name, string description, Importance importance)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Description = description;
|
||||
Group = null;
|
||||
this.Importance = importance;
|
||||
|
||||
CanBypassDnd = false;
|
||||
CanShowBadge = true;
|
||||
EnableLights = false;
|
||||
EnableVibration = true;
|
||||
VibrationPattern = null;
|
||||
|
||||
this.LockScreenVisibility = LockScreenVisibility.Public;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notification channel group description.
|
||||
/// It is optional to put channels into groups, but looks nicer in Settings UI.
|
||||
/// </summary>
|
||||
public struct AndroidNotificationChannelGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// A unique ID for this group. Will rename the group if already exists.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A user visible name for this group.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A description for this group.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f3829a6c0515ee45b4e641fbd0999f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Notifications.Android
|
||||
{
|
||||
internal static class AndroidNotificationExtensions
|
||||
{
|
||||
public static Importance ToImportance(this int importance)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(Importance), importance))
|
||||
return (Importance)importance;
|
||||
|
||||
return Importance.Default;
|
||||
}
|
||||
|
||||
public static LockScreenVisibility ToLockScreenVisibility(this int lockscreenVisibility)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(LockScreenVisibility), lockscreenVisibility))
|
||||
return (LockScreenVisibility)lockscreenVisibility;
|
||||
|
||||
return LockScreenVisibility.Public;
|
||||
}
|
||||
|
||||
public static NotificationStyle ToNotificationStyle(this int notificationStyle)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(NotificationStyle), notificationStyle))
|
||||
return (NotificationStyle)notificationStyle;
|
||||
|
||||
return NotificationStyle.None;
|
||||
}
|
||||
|
||||
public static GroupAlertBehaviours ToGroupAlertBehaviours(this int groupAlertBehaviour)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(GroupAlertBehaviours), groupAlertBehaviour))
|
||||
return (GroupAlertBehaviours)groupAlertBehaviour;
|
||||
|
||||
return GroupAlertBehaviours.GroupAlertAll;
|
||||
}
|
||||
|
||||
public static Color ToColor(this int color)
|
||||
{
|
||||
int a = (color >> 24) & 0xff;
|
||||
int r = (color >> 16) & 0xff;
|
||||
int g = (color >> 8) & 0xff;
|
||||
int b = (color) & 0xff;
|
||||
|
||||
return new Color32((byte)r, (byte)g, (byte)b, (byte)a);
|
||||
}
|
||||
|
||||
public static int ToInt(this Color? color)
|
||||
{
|
||||
if (!color.HasValue)
|
||||
return 0;
|
||||
|
||||
var color32 = (Color32)color.Value;
|
||||
return (color32.a & 0xff) << 24 | (color32.r & 0xff) << 16 | (color32.g & 0xff) << 8 | (color32.b & 0xff);
|
||||
}
|
||||
|
||||
public static long ToLong(this DateTime dateTime)
|
||||
{
|
||||
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
TimeSpan diff = dateTime.ToUniversalTime() - origin;
|
||||
|
||||
return (long)Math.Floor(diff.TotalMilliseconds);
|
||||
}
|
||||
|
||||
public static DateTime ToDatetime(this long dateTime)
|
||||
{
|
||||
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
return origin.AddMilliseconds(dateTime).ToLocalTime();
|
||||
}
|
||||
|
||||
public static long ToLong(this TimeSpan? timeSpan)
|
||||
{
|
||||
return timeSpan.HasValue ? (long)timeSpan.Value.TotalMilliseconds : -1L;
|
||||
}
|
||||
|
||||
public static TimeSpan ToTimeSpan(this long timeSpan)
|
||||
{
|
||||
return TimeSpan.FromMilliseconds(timeSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b97d256371bb62248bb9f4404a72030a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Notifications.Android
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for the AndroidNotification. Contains the notification's id and channel.
|
||||
/// </summary>
|
||||
public class AndroidNotificationIntentData
|
||||
{
|
||||
/// <summary>
|
||||
/// The id of the notification.
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The channel id that the notification was sent to.
|
||||
/// </summary>
|
||||
public string Channel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the AndroidNotification.
|
||||
/// </summary>
|
||||
public AndroidNotification Notification { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the proxy to the Android Java instance of Notification class.
|
||||
/// </summary>
|
||||
public AndroidJavaObject NativeNotification { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an AndroidNotificationIntentData with AndroidNotification, id, and channel id.
|
||||
/// </summary>
|
||||
/// <param name="id">Notification id</param>
|
||||
/// <param name="channelId">ID of the notification channel</param>
|
||||
/// <param name="notification">Data of the received notification</param>
|
||||
public AndroidNotificationIntentData(int id, string channelId, AndroidNotification notification)
|
||||
{
|
||||
Id = id;
|
||||
Channel = channelId;
|
||||
Notification = notification;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25b01cf50533ff64387fe382f4075a2c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Notifications.Android
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that queues the received notifications and triggers the notification callbacks.
|
||||
/// </summary>
|
||||
public class AndroidReceivedNotificationMainThreadDispatcher : MonoBehaviour
|
||||
{
|
||||
private static AndroidReceivedNotificationMainThreadDispatcher instance = null;
|
||||
|
||||
private List<AndroidJavaObject> m_ReceivedNotificationQueue = new List<AndroidJavaObject>();
|
||||
|
||||
private List<AndroidJavaObject> m_ReceivedNotificationList = new List<AndroidJavaObject>();
|
||||
|
||||
internal void EnqueueReceivedNotification(AndroidJavaObject notification)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
m_ReceivedNotificationQueue.Add(notification);
|
||||
}
|
||||
}
|
||||
|
||||
internal static AndroidReceivedNotificationMainThreadDispatcher GetInstance()
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update is called once per frame.
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
// Note: Don't call callbacks while locking receivedNotificationQueue, otherwise there's a risk
|
||||
// that callback might introduce an operations which would create a deadlock
|
||||
lock (this)
|
||||
{
|
||||
if (m_ReceivedNotificationQueue.Count == 0)
|
||||
return;
|
||||
var temp = m_ReceivedNotificationQueue;
|
||||
m_ReceivedNotificationQueue = m_ReceivedNotificationList;
|
||||
m_ReceivedNotificationList = temp;
|
||||
}
|
||||
|
||||
foreach (var notification in m_ReceivedNotificationList)
|
||||
{
|
||||
try
|
||||
{
|
||||
AndroidNotificationCenter.ReceivedNotificationCallback(notification);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
m_ReceivedNotificationList.Clear();
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = this;
|
||||
DontDestroyOnLoad(this.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 253fc88305aec43f3886f5484cfbb307
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Android.Notifications.Tests")]
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76998f4bffc9142d18bb6fa7e954e01c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
using UnityEngine.Android;
|
||||
|
||||
namespace Unity.Notifications.Android
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a status of the Android runtime permission.
|
||||
/// </summary>
|
||||
public enum PermissionStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// No permission as user was not prompted for it.
|
||||
/// </summary>
|
||||
NotRequested = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User gave permission.
|
||||
/// </summary>
|
||||
Allowed = 1,
|
||||
|
||||
/// <summary>
|
||||
/// User denied permission.
|
||||
/// </summary>
|
||||
Denied = 2,
|
||||
|
||||
/// <summary>
|
||||
/// No longer used. User denied permission and expressed intent to not be prompted again.
|
||||
/// </summary>
|
||||
DeniedDontAskAgain = 3,
|
||||
|
||||
/// <summary>
|
||||
/// A request for permission was made and user hasn't responded yet.
|
||||
/// </summary>
|
||||
RequestPending = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Notifications are blocked for this app. Before API level 33 this means they were disabled in Settings.
|
||||
/// <see cref="https://developer.android.com/reference/android/app/NotificationManager#areNotificationsEnabled()"/>
|
||||
/// </summary>
|
||||
NotificationsBlockedForApp = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A class to request permission to post notifications.
|
||||
/// Before Android 13 (API 33) it is not required and Status will become Allowed immediately.
|
||||
/// May succeed or fail immediately. Users response is saved to PlayerPrefs.
|
||||
/// Respects users wish to not be asked again.
|
||||
/// </summary>
|
||||
/// <seealso cref="AndroidNotificationCenter.UserPermissionToPost"/>
|
||||
/// <seealso cref="AndroidNotificationCenter.SETTING_POST_NOTIFICATIONS_PERMISSION"/>
|
||||
public class PermissionRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The status of this request.
|
||||
/// Value other than RequestPending means request has completed.
|
||||
/// </summary>
|
||||
public PermissionStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new request.
|
||||
/// Will show user a dialog asking for permission if that is required to post notifications and user hasn't permanently denied it already.
|
||||
/// </summary>
|
||||
public PermissionRequest()
|
||||
{
|
||||
Status = AndroidNotificationCenter.UserPermissionToPost;
|
||||
switch (Status)
|
||||
{
|
||||
case PermissionStatus.NotRequested:
|
||||
case PermissionStatus.Denied:
|
||||
case PermissionStatus.DeniedDontAskAgain: // this one is no longer used, but might be found in settings
|
||||
Status = RequestPermission();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PermissionStatus RequestPermission()
|
||||
{
|
||||
if (!AndroidNotificationCenter.CanRequestPermissionToPost)
|
||||
return PermissionStatus.Denied;
|
||||
var callbacks = new PermissionCallbacks();
|
||||
callbacks.PermissionGranted += (unused) => PermissionResponse(PermissionStatus.Allowed);
|
||||
callbacks.PermissionDenied += (unused) => PermissionResponse(PermissionStatus.Denied);
|
||||
Permission.RequestUserPermission(AndroidNotificationCenter.PERMISSION_POST_NOTIFICATIONS, callbacks);
|
||||
return PermissionStatus.RequestPending;
|
||||
}
|
||||
|
||||
void PermissionResponse(PermissionStatus status)
|
||||
{
|
||||
Status = status;
|
||||
AndroidNotificationCenter.SetPostPermissionSetting(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 110fbd120961b4105a52b39fc24273be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4d5e3b0fd3074704a1c4fcee255f85c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a06b648d726ee0479a00bac712c49f1
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
: Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 0
|
||||
Exclude Editor: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
Exclude iOS: 1
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARMv7
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
- first:
|
||||
Standalone: Linux64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
CPU: AnyCPU
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
def getCompileSdk(unityLib) {
|
||||
def unityCompileSdk = unityLib.compileSdkVersion
|
||||
def version = unityCompileSdk.find('\\d+').toInteger()
|
||||
if (version < 33) {
|
||||
return 33
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
android {
|
||||
// Checking if namespace exists is needed for 2021.3 (AGP 4.0.1)
|
||||
// When 2021.3 is dropped, remove this check and package="com.unity.androidnotifications" from AndroidManifest
|
||||
if (project.android.hasProperty("namespace")) {
|
||||
namespace "com.unity.androidnotifications"
|
||||
}
|
||||
|
||||
def unityLib = project(':unityLibrary').extensions.getByName('android')
|
||||
compileSdkVersion getCompileSdk(unityLib)
|
||||
buildToolsVersion unityLib.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 22
|
||||
consumerProguardFiles "proguard-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api files('../libs/unity-classes.jar')
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Accessed from C# code
|
||||
-keep class com.unity.androidnotifications.UnityNotificationManager { public *; }
|
||||
-keep interface com.unity.androidnotifications.NotificationCallback { *; }
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity.androidnotifications">
|
||||
<application>
|
||||
<receiver android:name="com.unity.androidnotifications.UnityNotificationManager" android:exported="false" />
|
||||
<receiver android:name="com.unity.androidnotifications.UnityNotificationRestartReceiver" android:enabled="false" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<meta-data android:name="com.unity.androidnotifications.exact_scheduling" android:value="0" />
|
||||
</application>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
</manifest>
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
package com.unity.androidnotifications;
|
||||
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_FIRE_TIME;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_ID;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.TAG_UNITY;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class UnityNotificationBackgroundThread extends Thread {
|
||||
private static abstract class Task {
|
||||
// returns true if notificationIds was modified (needs to be saved)
|
||||
public abstract boolean run(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> notifications);
|
||||
}
|
||||
|
||||
private static class ScheduleNotificationTask extends Task {
|
||||
private int notificationId;
|
||||
private Notification.Builder notificationBuilder;
|
||||
private boolean isCustomized;
|
||||
private boolean isNew;
|
||||
|
||||
public ScheduleNotificationTask(int id, Notification.Builder builder, boolean customized, boolean addedNew) {
|
||||
notificationId = id;
|
||||
notificationBuilder = builder;
|
||||
isCustomized = customized;
|
||||
isNew = addedNew;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
|
||||
String id = String.valueOf(notificationId);
|
||||
Integer ID = Integer.valueOf(notificationId);
|
||||
boolean didSchedule = false;
|
||||
try {
|
||||
UnityNotificationManager.mUnityNotificationManager.performNotificationScheduling(notificationId, notificationBuilder, isCustomized);
|
||||
didSchedule = true;
|
||||
} finally {
|
||||
// if failed to schedule or replace, remove
|
||||
if (!didSchedule) {
|
||||
notifications.remove(notificationId);
|
||||
manager.cancelPendingNotificationIntent(notificationId);
|
||||
manager.deleteExpiredNotificationIntent(id);
|
||||
}
|
||||
}
|
||||
|
||||
return isNew;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CancelNotificationTask extends Task {
|
||||
private int notificationId;
|
||||
|
||||
public CancelNotificationTask(int id) {
|
||||
notificationId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
|
||||
manager.cancelPendingNotificationIntent(notificationId);
|
||||
if (notifications.remove(notificationId) != null) {
|
||||
manager.deleteExpiredNotificationIntent(String.valueOf(notificationId));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CancelAllNotificationsTask extends Task {
|
||||
@Override
|
||||
public boolean run(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
|
||||
if (notifications.isEmpty())
|
||||
return false;
|
||||
|
||||
Enumeration<Integer> ids = notifications.keys();
|
||||
while (ids.hasMoreElements()) {
|
||||
Integer notificationId = ids.nextElement();
|
||||
manager.cancelPendingNotificationIntent(notificationId);
|
||||
manager.deleteExpiredNotificationIntent(String.valueOf(notificationId));
|
||||
}
|
||||
|
||||
notifications.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HousekeepingTask extends Task {
|
||||
UnityNotificationBackgroundThread thread;
|
||||
|
||||
public HousekeepingTask(UnityNotificationBackgroundThread th) {
|
||||
thread = th;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
|
||||
HashSet<String> notificationIds = new HashSet<>();
|
||||
Enumeration<Integer> ids = notifications.keys();
|
||||
while (ids.hasMoreElements()) {
|
||||
notificationIds.add(String.valueOf(ids.nextElement()));
|
||||
}
|
||||
thread.performHousekeeping(notificationIds);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int TASKS_FOR_HOUSEKEEPING = 50;
|
||||
private LinkedTransferQueue<Task> mTasks = new LinkedTransferQueue();
|
||||
private ConcurrentHashMap<Integer, Notification.Builder> mScheduledNotifications;
|
||||
private UnityNotificationManager mManager;
|
||||
private int mTasksSinceHousekeeping = TASKS_FOR_HOUSEKEEPING; // we want hoursekeeping at the start
|
||||
|
||||
public UnityNotificationBackgroundThread(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> scheduledNotifications) {
|
||||
mManager = manager;
|
||||
mScheduledNotifications = scheduledNotifications;
|
||||
// rescheduling after reboot may have loaded, otherwise load here
|
||||
if (mScheduledNotifications.size() == 0)
|
||||
loadNotifications();
|
||||
}
|
||||
|
||||
public void enqueueNotification(int id, Notification.Builder notificationBuilder, boolean customized, boolean addedNew) {
|
||||
mTasks.add(new UnityNotificationBackgroundThread.ScheduleNotificationTask(id, notificationBuilder, customized, addedNew));
|
||||
}
|
||||
|
||||
public void enqueueCancelNotification(int id) {
|
||||
mTasks.add(new CancelNotificationTask(id));
|
||||
}
|
||||
|
||||
public void enqueueCancelAllNotifications() {
|
||||
mTasks.add(new CancelAllNotificationsTask());
|
||||
}
|
||||
|
||||
private void enqueueHousekeeping() {
|
||||
mTasks.add(new HousekeepingTask(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean haveChanges = false;
|
||||
while (true) {
|
||||
try {
|
||||
Task task = mTasks.take();
|
||||
haveChanges |= executeTask(mManager, task, mScheduledNotifications);
|
||||
if (!(task instanceof HousekeepingTask))
|
||||
++mTasksSinceHousekeeping;
|
||||
if (mTasks.size() == 0 && haveChanges) {
|
||||
haveChanges = false;
|
||||
enqueueHousekeeping();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
if (mTasks.isEmpty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean executeTask(UnityNotificationManager manager, Task task, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
|
||||
try {
|
||||
return task.run(manager, notifications);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Exception executing notification task", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void performHousekeeping(Set<String> notificationIds) {
|
||||
// don't do housekeeping if last task we did was housekeeping (other=1)
|
||||
boolean performHousekeeping = mTasksSinceHousekeeping >= TASKS_FOR_HOUSEKEEPING;
|
||||
mTasksSinceHousekeeping = 0;
|
||||
if (performHousekeeping)
|
||||
mManager.performNotificationHousekeeping(notificationIds);
|
||||
mManager.saveScheduledNotificationIDs(notificationIds);
|
||||
}
|
||||
|
||||
private void loadNotifications() {
|
||||
List<Notification.Builder> notifications = mManager.loadSavedNotifications();
|
||||
if (notifications == null || notifications.size() == 0)
|
||||
return;
|
||||
final long currentTime = Calendar.getInstance().getTime().getTime();
|
||||
boolean needHousekeeping = false;
|
||||
for (Notification.Builder builder : notifications) {
|
||||
Bundle extras = builder.getExtras();
|
||||
int id = extras.getInt(KEY_ID, -1);
|
||||
long fireTime = extras.getLong(KEY_FIRE_TIME, -1);
|
||||
boolean inFuture = fireTime - currentTime > 0;
|
||||
if (inFuture)
|
||||
mScheduledNotifications.put(id, builder);
|
||||
else
|
||||
needHousekeeping = true;
|
||||
}
|
||||
|
||||
if (needHousekeeping)
|
||||
enqueueHousekeeping();
|
||||
}
|
||||
}
|
||||
+1117
File diff suppressed because it is too large
Load Diff
+67
@@ -0,0 +1,67 @@
|
||||
package com.unity.androidnotifications;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_FIRE_TIME;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_ID;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_REPEAT_INTERVAL;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.TAG_UNITY;
|
||||
|
||||
public class UnityNotificationRestartReceiver extends BroadcastReceiver {
|
||||
private static final long EXPIRATION_TRESHOLD = 600000; // 10 minutes
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent received_intent) {
|
||||
Log.d(TAG_UNITY, "Rescheduling notifications after restart");
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(received_intent.getAction())) {
|
||||
AsyncTask.execute(() -> { rescheduleSavedNotifications(context); });
|
||||
}
|
||||
}
|
||||
|
||||
private static void rescheduleSavedNotifications(Context context) {
|
||||
UnityNotificationManager manager = UnityNotificationManager.getNotificationManagerImpl(context);
|
||||
List<Notification.Builder> saved_notifications = manager.loadSavedNotifications();
|
||||
Date currentDate = Calendar.getInstance().getTime();
|
||||
|
||||
for (Notification.Builder notificationBuilder : saved_notifications) {
|
||||
rescheduleNotification(manager, currentDate, notificationBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean rescheduleNotification(UnityNotificationManager manager, Date currentDate, Notification.Builder notificationBuilder) {
|
||||
try {
|
||||
Bundle extras = notificationBuilder.getExtras();
|
||||
long repeatInterval = extras.getLong(KEY_REPEAT_INTERVAL, 0L);
|
||||
long fireTime = extras.getLong(KEY_FIRE_TIME, 0L);
|
||||
Date fireTimeDate = new Date(fireTime);
|
||||
|
||||
boolean isRepeatable = repeatInterval > 0;
|
||||
|
||||
if (fireTimeDate.after(currentDate) || isRepeatable) {
|
||||
manager.scheduleAlarmWithNotification(notificationBuilder);
|
||||
return true;
|
||||
} else if (currentDate.getTime() - fireTime < EXPIRATION_TRESHOLD) {
|
||||
// notification is in the past, but not by much, send now
|
||||
int id = extras.getInt(KEY_ID);
|
||||
manager.notify(id, notificationBuilder);
|
||||
return true;
|
||||
} else {
|
||||
Log.d(TAG_UNITY, "Notification expired, not rescheduling, ID: " + extras.getInt(KEY_ID, -1));
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Failed to reschedule notification", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+652
@@ -0,0 +1,652 @@
|
||||
package com.unity.androidnotifications;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_CHANNEL_ID;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_FIRE_TIME;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_ID;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_INTENT_DATA;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_LARGE_ICON;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_NOTIFICATION;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_REPEAT_INTERVAL;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_SMALL_ICON;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_SHOW_IN_FOREGROUND;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_LARGE_ICON;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_PICTURE;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_CONTENT_TITLE;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_SUMMARY_TEXT;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_CONTENT_DESCRIPTION;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_SHOW_WHEN_COLLAPSED;
|
||||
import static com.unity.androidnotifications.UnityNotificationManager.TAG_UNITY;
|
||||
|
||||
class UnityNotificationUtilities {
|
||||
/*
|
||||
We serialize notifications and save them to shared prefs, so that if app is killed, we can recreate them.
|
||||
The serialized BLOB starts with a four byte magic number descibing serialization type, followed by an integer version.
|
||||
IMPORTANT: IF YOU DO A CHANGE THAT AFFECTS THE LAYOUT, BUMP THE VERSION, AND ENSURE OLD VERSION STILL DESERIALIZES. ADD TEST.
|
||||
In real life app can get updated having old serialized notifications present, so we should be able to deserialize them.
|
||||
*/
|
||||
// magic stands for "Unity Mobile Notifications Notification"
|
||||
static final byte[] UNITY_MAGIC_NUMBER = new byte[] { 'U', 'M', 'N', 'N'};
|
||||
private static final byte[] UNITY_MAGIC_NUMBER_PARCELLED = new byte[] { 'U', 'M', 'N', 'P'};
|
||||
private static final int NOTIFICATION_SERIALIZATION_VERSION = 3;
|
||||
private static final int INTENT_SERIALIZATION_VERSION = 0;
|
||||
|
||||
static final String SAVED_NOTIFICATION_PRIMARY_KEY = "data";
|
||||
static final String SAVED_NOTIFICATION_FALLBACK_KEY = "fallback.data";
|
||||
|
||||
protected static int findResourceIdInContextByName(Context context, String name) {
|
||||
if (name == null)
|
||||
return 0;
|
||||
|
||||
try {
|
||||
Resources res = context.getResources();
|
||||
if (res != null) {
|
||||
int id = res.getIdentifier(name, "mipmap", context.getPackageName());
|
||||
if (id == 0)
|
||||
return res.getIdentifier(name, "drawable", context.getPackageName());
|
||||
else
|
||||
return id;
|
||||
}
|
||||
return 0;
|
||||
} catch (Resources.NotFoundException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Originally we used to serialize a bundle with predefined list of values.
|
||||
After we exposed entire Notification.Builder to users, this is not sufficient anymore.
|
||||
Unfortunately, while Notification itself is Parcelable and can be marshalled to bytes,
|
||||
it's contents are not guaranteed to be (Binder objects).
|
||||
Hence what we try to do here is:
|
||||
- serialize as is if notification is possibly customized by user
|
||||
- otherwise serialize our stuff, since there is nothing more
|
||||
*/
|
||||
protected static void serializeNotification(SharedPreferences prefs, Notification notification, boolean serializeParcel) {
|
||||
try {
|
||||
String serialized;
|
||||
ByteArrayOutputStream data = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(data);
|
||||
if (serializeParcel) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(KEY_NOTIFICATION, notification);
|
||||
if (serializeNotificationParcel(intent, out)) {
|
||||
out.close();
|
||||
byte[] bytes = data.toByteArray();
|
||||
serialized = Base64.encodeToString(bytes, 0, bytes.length, 0);
|
||||
} else {
|
||||
return; // failed
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (serializeNotificationCustom(notification, out)) {
|
||||
out.flush();
|
||||
byte[] bytes = data.toByteArray();
|
||||
serialized = Base64.encodeToString(bytes, 0, bytes.length, 0);
|
||||
} else {
|
||||
return; // failed
|
||||
}
|
||||
}
|
||||
|
||||
SharedPreferences.Editor editor = prefs.edit().clear();
|
||||
editor.putString(SAVED_NOTIFICATION_PRIMARY_KEY, serialized);
|
||||
editor.apply();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Failed to serialize notification", e);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean serializeNotificationParcel(Intent intent, DataOutputStream out) {
|
||||
try {
|
||||
byte[] bytes = serializeParcelable(intent);
|
||||
if (bytes == null || bytes.length == 0)
|
||||
return false;
|
||||
out.write(UNITY_MAGIC_NUMBER_PARCELLED);
|
||||
out.writeInt(INTENT_SERIALIZATION_VERSION);
|
||||
out.writeInt(bytes.length);
|
||||
out.write(bytes);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Failed to serialize notification as Parcel", e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e(TAG_UNITY, "Failed to serialize notification as Parcel", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean serializeNotificationCustom(Notification notification, DataOutputStream out) {
|
||||
try {
|
||||
out.write(UNITY_MAGIC_NUMBER);
|
||||
out.writeInt(NOTIFICATION_SERIALIZATION_VERSION);
|
||||
|
||||
// serialize extras
|
||||
boolean showWhen = notification.extras.getBoolean(Notification.EXTRA_SHOW_WHEN, false);
|
||||
|
||||
out.writeInt(notification.extras.getInt(KEY_ID));
|
||||
serializeString(out, notification.extras.getString(Notification.EXTRA_TITLE));
|
||||
serializeString(out, notification.extras.getString(Notification.EXTRA_TEXT));
|
||||
serializeString(out, notification.extras.getString(KEY_SMALL_ICON));
|
||||
serializeString(out, notification.extras.getString(KEY_LARGE_ICON));
|
||||
out.writeLong(notification.extras.getLong(KEY_FIRE_TIME, -1));
|
||||
out.writeLong(notification.extras.getLong(KEY_REPEAT_INTERVAL, -1));
|
||||
serializeString(out, notification.extras.getString(Notification.EXTRA_BIG_TEXT));
|
||||
out.writeBoolean(notification.extras.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER, false));
|
||||
out.writeBoolean(showWhen);
|
||||
serializeString(out, notification.extras.getString(KEY_INTENT_DATA));
|
||||
out.writeBoolean(notification.extras.getBoolean(KEY_SHOW_IN_FOREGROUND, true));
|
||||
|
||||
String bigPicture = notification.extras.getString(KEY_BIG_PICTURE);
|
||||
serializeString(out, bigPicture);
|
||||
if (bigPicture != null && bigPicture.length() > 0) {
|
||||
// the following only need to be put in if big picture is there
|
||||
serializeString(out, notification.extras.getString(KEY_BIG_LARGE_ICON));
|
||||
serializeString(out, notification.extras.getString(KEY_BIG_CONTENT_TITLE));
|
||||
serializeString(out, notification.extras.getString(KEY_BIG_CONTENT_DESCRIPTION));
|
||||
serializeString(out, notification.extras.getString(KEY_BIG_SUMMARY_TEXT));
|
||||
out.writeBoolean(notification.extras.getBoolean(KEY_BIG_SHOW_WHEN_COLLAPSED, false));
|
||||
}
|
||||
|
||||
serializeString(out, Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? null : notification.getChannelId());
|
||||
Integer color = UnityNotificationManager.getNotificationColor(notification);
|
||||
out.writeBoolean(color != null);
|
||||
if (color != null)
|
||||
out.writeInt(color);
|
||||
out.writeInt(notification.number);
|
||||
out.writeBoolean(0 != (notification.flags & Notification.FLAG_AUTO_CANCEL));
|
||||
serializeString(out, notification.getGroup());
|
||||
out.writeBoolean(0 != (notification.flags & Notification.FLAG_GROUP_SUMMARY));
|
||||
out.writeInt(UnityNotificationManager.getNotificationGroupAlertBehavior(notification));
|
||||
serializeString(out, notification.getSortKey());
|
||||
if (showWhen)
|
||||
out.writeLong(notification.when);
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Failed to serialize notification", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void serializeString(DataOutputStream out, String s) throws IOException {
|
||||
if (s == null || s.length() == 0)
|
||||
out.writeInt(0);
|
||||
else {
|
||||
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
|
||||
out.writeInt(bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] serializeParcelable(Parcelable obj) {
|
||||
try {
|
||||
Parcel p = Parcel.obtain();
|
||||
Bundle b = new Bundle();
|
||||
b.putParcelable("obj", obj);
|
||||
p.writeParcelable(b, 0);
|
||||
byte[] result = p.marshall();
|
||||
p.recycle();
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Failed to serialize Parcelable", e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e(TAG_UNITY, "Failed to serialize Parcelable", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static Object deserializeNotification(Context context, SharedPreferences prefs) {
|
||||
String serializedIntentData = prefs.getString(SAVED_NOTIFICATION_PRIMARY_KEY, "");
|
||||
if (null == serializedIntentData || serializedIntentData.length() <= 0)
|
||||
return null;
|
||||
byte[] bytes = Base64.decode(serializedIntentData, 0);
|
||||
Object notification = deserializeNotification(context, bytes);
|
||||
if (notification != null)
|
||||
return notification;
|
||||
serializedIntentData = prefs.getString(SAVED_NOTIFICATION_FALLBACK_KEY, "");
|
||||
if (null == serializedIntentData || serializedIntentData.length() <= 0)
|
||||
return null;
|
||||
bytes = Base64.decode(serializedIntentData, 0);
|
||||
return deserializeNotification(context, bytes);
|
||||
}
|
||||
|
||||
/* See serialization method above for explaination of fallbacks.
|
||||
This one matches it with one additional fallback: support for "old" bundle serialization.
|
||||
*/
|
||||
private static Object deserializeNotification(Context context, byte[] bytes) {
|
||||
ByteArrayInputStream data = new ByteArrayInputStream(bytes);
|
||||
DataInputStream in = new DataInputStream(data);
|
||||
Notification notification = deserializeNotificationParcelable(in);
|
||||
if (notification != null)
|
||||
return notification;
|
||||
data.reset();
|
||||
Notification.Builder builder = deserializeNotificationCustom(context, in);
|
||||
if (builder == null) {
|
||||
builder = deserializedFromOldIntent(context, bytes);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static boolean readAndCheckMagicNumber(DataInputStream in, byte[] magic) {
|
||||
try {
|
||||
boolean magicNumberMatch = true;
|
||||
for (int i = 0; i < magic.length; ++i) {
|
||||
byte b = in.readByte();
|
||||
if (b != magic[i]) {
|
||||
magicNumberMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return magicNumberMatch;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Notification deserializeNotificationParcelable(DataInputStream in) {
|
||||
try {
|
||||
if (!readAndCheckMagicNumber(in, UNITY_MAGIC_NUMBER_PARCELLED))
|
||||
return null;
|
||||
int version = in.readInt();
|
||||
if (version < 0 || version > INTENT_SERIALIZATION_VERSION)
|
||||
return null;
|
||||
Intent intent = deserializeParcelable(in);
|
||||
Notification notification = intent.getParcelableExtra(KEY_NOTIFICATION);
|
||||
return notification;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Failed to deserialize notification intent", e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e(TAG_UNITY, "Failed to deserialize notification intent", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Notification.Builder deserializeNotificationCustom(Context context, DataInputStream in) {
|
||||
try {
|
||||
if (!readAndCheckMagicNumber(in, UNITY_MAGIC_NUMBER))
|
||||
return null;
|
||||
int version = in.readInt();
|
||||
if (version < 0 || version > NOTIFICATION_SERIALIZATION_VERSION)
|
||||
return null;
|
||||
|
||||
// deserialize extras
|
||||
int id = 0;
|
||||
String title, text, smallIcon, largeIcon, bigText, intentData;
|
||||
long fireTime, repeatInterval;
|
||||
boolean usesStopWatch, showWhen, showInForeground = true;
|
||||
Bundle extras = null;
|
||||
String bigPicture = null, bigLargeIcon = null, bigContentTitle = null, bigSummary = null, bigContentDesc = null;
|
||||
boolean bigShowWhenCollapsed = false;
|
||||
if (version < 2) {
|
||||
// no longer serialized since v2
|
||||
extras = deserializeParcelable(in);
|
||||
}
|
||||
|
||||
// before v2 it was extras or variables, since 2 always variables
|
||||
if (extras == null) {
|
||||
// extras serialized manually
|
||||
id = in.readInt();
|
||||
title = deserializeString(in);
|
||||
text = deserializeString(in);
|
||||
smallIcon = deserializeString(in);
|
||||
largeIcon = deserializeString(in);
|
||||
fireTime = in.readLong();
|
||||
repeatInterval = in.readLong();
|
||||
bigText = deserializeString(in);
|
||||
usesStopWatch = in.readBoolean();
|
||||
showWhen = in.readBoolean();
|
||||
intentData = deserializeString(in);
|
||||
if (version > 0)
|
||||
showInForeground = in.readBoolean();
|
||||
|
||||
if (version >= 3) {
|
||||
bigPicture = deserializeString(in);
|
||||
if (bigPicture != null && bigPicture.length() > 0) {
|
||||
// the following only need to be put in if big picture is there
|
||||
bigLargeIcon = deserializeString(in);
|
||||
bigContentTitle = deserializeString(in);
|
||||
bigContentDesc = deserializeString(in);
|
||||
bigSummary = deserializeString(in);
|
||||
bigShowWhenCollapsed = in.readBoolean();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
title = extras.getString(Notification.EXTRA_TITLE);
|
||||
text = extras.getString(Notification.EXTRA_TEXT);
|
||||
smallIcon = extras.getString(KEY_SMALL_ICON);
|
||||
largeIcon = extras.getString(KEY_LARGE_ICON);
|
||||
fireTime = extras.getLong(KEY_FIRE_TIME, -1);
|
||||
repeatInterval = extras.getLong(KEY_REPEAT_INTERVAL, -1);
|
||||
bigText = extras.getString(Notification.EXTRA_BIG_TEXT);
|
||||
usesStopWatch = extras.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER, false);
|
||||
showWhen = extras.getBoolean(Notification.EXTRA_SHOW_WHEN, false);
|
||||
intentData = extras.getString(KEY_INTENT_DATA);
|
||||
}
|
||||
|
||||
String channelId = deserializeString(in);
|
||||
boolean haveColor = in.readBoolean();
|
||||
int color = 0;
|
||||
if (haveColor)
|
||||
color = in.readInt();
|
||||
int number = in.readInt();
|
||||
boolean shouldAutoCancel = in.readBoolean();
|
||||
String group = deserializeString(in);
|
||||
boolean groupSummary = in.readBoolean();
|
||||
int groupAlertBehavior = in.readInt();
|
||||
String sortKey = deserializeString(in);
|
||||
long when = showWhen ? in.readLong() : 0;
|
||||
|
||||
UnityNotificationManager manager = UnityNotificationManager.getNotificationManagerImpl(context);
|
||||
Notification.Builder builder = manager.createNotificationBuilder(channelId);
|
||||
if (extras != null)
|
||||
builder.setExtras(extras);
|
||||
else {
|
||||
builder.getExtras().putInt(KEY_ID, id);
|
||||
UnityNotificationManager.setNotificationIcon(builder, KEY_SMALL_ICON, smallIcon);
|
||||
UnityNotificationManager.setNotificationIcon(builder, KEY_LARGE_ICON, largeIcon);
|
||||
if (fireTime != -1)
|
||||
builder.getExtras().putLong(KEY_FIRE_TIME, fireTime);
|
||||
if (repeatInterval != -1)
|
||||
builder.getExtras().putLong(KEY_REPEAT_INTERVAL, repeatInterval);
|
||||
if (intentData != null)
|
||||
builder.getExtras().putString(KEY_INTENT_DATA, intentData);
|
||||
builder.getExtras().putBoolean(KEY_SHOW_IN_FOREGROUND, showInForeground);
|
||||
}
|
||||
if (title != null)
|
||||
builder.setContentTitle(title);
|
||||
if (text != null)
|
||||
builder.setContentText(text);
|
||||
if (bigText != null)
|
||||
builder.setStyle(new Notification.BigTextStyle().bigText(bigText));
|
||||
else if (bigPicture != null)
|
||||
manager.setupBigPictureStyle(builder, bigLargeIcon, bigPicture, bigContentTitle, bigContentDesc, bigSummary, bigShowWhenCollapsed);
|
||||
if (haveColor)
|
||||
UnityNotificationManager.setNotificationColor(builder, color);
|
||||
if (number >= 0)
|
||||
builder.setNumber(number);
|
||||
builder.setAutoCancel(shouldAutoCancel);
|
||||
UnityNotificationManager.setNotificationUsesChronometer(builder, usesStopWatch);
|
||||
if (group != null && group.length() > 0)
|
||||
builder.setGroup(group);
|
||||
builder.setGroupSummary(groupSummary);
|
||||
UnityNotificationManager.setNotificationGroupAlertBehavior(builder, groupAlertBehavior);
|
||||
if (sortKey != null && sortKey.length() > 0)
|
||||
builder.setSortKey(sortKey);
|
||||
if (showWhen) {
|
||||
builder.setShowWhen(true);
|
||||
builder.setWhen(when);
|
||||
}
|
||||
|
||||
return builder;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Failed to deserialize notification", e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e(TAG_UNITY, "Failed to deserialize notification", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Notification.Builder deserializedFromOldIntent(Context context, byte[] bytes) {
|
||||
try {
|
||||
Parcel p = Parcel.obtain();
|
||||
p.unmarshall(bytes, 0, bytes.length);
|
||||
p.setDataPosition(0);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.readFromParcel(p);
|
||||
|
||||
int id = bundle.getInt(KEY_ID, -1);
|
||||
String channelId = bundle.getString("channelID");
|
||||
String textTitle = bundle.getString("textTitle");
|
||||
String textContent = bundle.getString("textContent");
|
||||
String smallIcon = bundle.getString("smallIconStr");
|
||||
boolean autoCancel = bundle.getBoolean("autoCancel", false);
|
||||
boolean usesChronometer = bundle.getBoolean("usesChronometer", false);
|
||||
long fireTime = bundle.getLong(KEY_FIRE_TIME, -1);
|
||||
long repeatInterval = bundle.getLong(KEY_REPEAT_INTERVAL, -1);
|
||||
String largeIcon = bundle.getString("largeIconStr");
|
||||
int style = bundle.getInt("style", -1);
|
||||
int color = bundle.getInt("color", 0);
|
||||
int number = bundle.getInt("number", 0);
|
||||
String intentData = bundle.getString(KEY_INTENT_DATA);
|
||||
String group = bundle.getString("group");
|
||||
boolean groupSummary = bundle.getBoolean("groupSummary", false);
|
||||
String sortKey = bundle.getString("sortKey");
|
||||
int groupAlertBehaviour = bundle.getInt("groupAlertBehaviour", -1);
|
||||
boolean showTimestamp = bundle.getBoolean("showTimestamp", false);
|
||||
|
||||
Notification.Builder builder = UnityNotificationManager.getNotificationManagerImpl(context).createNotificationBuilder(channelId);
|
||||
builder.getExtras().putInt(KEY_ID, id);
|
||||
builder.setContentTitle(textTitle);
|
||||
builder.setContentText(textContent);
|
||||
UnityNotificationManager.setNotificationIcon(builder, KEY_SMALL_ICON, smallIcon);
|
||||
builder.setAutoCancel(autoCancel);
|
||||
builder.setUsesChronometer(usesChronometer);
|
||||
builder.getExtras().putLong(KEY_FIRE_TIME, fireTime);
|
||||
builder.getExtras().putLong(KEY_REPEAT_INTERVAL, repeatInterval);
|
||||
UnityNotificationManager.setNotificationIcon(builder, KEY_LARGE_ICON, largeIcon);
|
||||
if (style == 2)
|
||||
builder.setStyle(new Notification.BigTextStyle().bigText(textContent));
|
||||
if (color != 0)
|
||||
UnityNotificationManager.setNotificationColor(builder, color);
|
||||
if (number >= 0)
|
||||
builder.setNumber(number);
|
||||
if (intentData != null)
|
||||
builder.getExtras().putString(KEY_INTENT_DATA, intentData);
|
||||
if (null != group && group.length() > 0)
|
||||
builder.setGroup(group);
|
||||
builder.setGroupSummary(groupSummary);
|
||||
if (null != sortKey && sortKey.length() > 0)
|
||||
builder.setSortKey(sortKey);
|
||||
UnityNotificationManager.setNotificationGroupAlertBehavior(builder, groupAlertBehaviour);
|
||||
builder.setShowWhen(showTimestamp);
|
||||
return builder;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Failed to deserialize old style notification", e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e(TAG_UNITY, "Failed to deserialize old style notification", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String deserializeString(DataInputStream in) throws IOException {
|
||||
int length = in.readInt();
|
||||
if (length <= 0)
|
||||
return null;
|
||||
byte[] bytes = new byte[length];
|
||||
int didRead = in.read(bytes);
|
||||
if (didRead != bytes.length)
|
||||
throw new IOException("Insufficient amount of bytes read");
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static <T extends Parcelable> T deserializeParcelable(DataInputStream in) throws IOException {
|
||||
int length = in.readInt();
|
||||
if (length <= 0)
|
||||
return null;
|
||||
byte[] bytes = new byte[length];
|
||||
int didRead = in.read(bytes);
|
||||
if (didRead != bytes.length)
|
||||
throw new IOException("Insufficient amount of bytes read");
|
||||
|
||||
try {
|
||||
Parcel p = Parcel.obtain();
|
||||
p.unmarshall(bytes, 0, bytes.length);
|
||||
p.setDataPosition(0);
|
||||
Bundle b = p.readParcelable(UnityNotificationUtilities.class.getClassLoader());
|
||||
p.recycle();
|
||||
if (b != null) {
|
||||
return b.getParcelable("obj");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Failed to deserialize parcelable", e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e(TAG_UNITY, "Failed to deserialize parcelable", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Returns Activity class to be opened when notification is tapped
|
||||
// Search is done in this order:
|
||||
// * class specified in meta-data key custom_notification_android_activity
|
||||
// * the only enabled activity with name ending in either .UnityPlayerActivity or .UnityPlayerGameActivity
|
||||
// * the only enabled activity in the package
|
||||
protected static Class<?> getOpenAppActivity(Context context) {
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ApplicationInfo ai = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
|
||||
Bundle bundle = ai.metaData;
|
||||
|
||||
if (bundle.containsKey("custom_notification_android_activity")) {
|
||||
try {
|
||||
return Class.forName(bundle.getString("custom_notification_android_activity"));
|
||||
} catch (ClassNotFoundException e) {
|
||||
Log.e(TAG_UNITY, "Specified activity class for notifications not found: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG_UNITY, "No custom_notification_android_activity found, attempting to find app activity class");
|
||||
|
||||
ActivityInfo[] aInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES).activities;
|
||||
if (aInfo == null) {
|
||||
Log.e(TAG_UNITY, "Could not get package activities");
|
||||
return null;
|
||||
}
|
||||
|
||||
String activityClassName = null;
|
||||
boolean activityIsUnity = false, activityConflict = false;
|
||||
for (ActivityInfo info : aInfo) {
|
||||
// activity alias not supported
|
||||
if (!info.enabled || info.targetActivity != null)
|
||||
continue;
|
||||
|
||||
boolean candidateIsUnity = isUnityActivity(info.name);
|
||||
if (activityClassName == null) {
|
||||
activityClassName = info.name;
|
||||
activityIsUnity = candidateIsUnity;
|
||||
continue;
|
||||
}
|
||||
|
||||
// two Unity activities is a hard conflict
|
||||
// two non-Unity activities is a conflict unless we find a Unity activity later on
|
||||
if (activityIsUnity == candidateIsUnity) {
|
||||
activityConflict = true;
|
||||
if (activityIsUnity && candidateIsUnity)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidateIsUnity) {
|
||||
activityClassName = info.name;
|
||||
activityIsUnity = candidateIsUnity;
|
||||
activityConflict = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (activityConflict) {
|
||||
Log.e(TAG_UNITY, "Multiple choices for activity for notifications, set activity explicitly in Notification Settings");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (activityClassName == null) {
|
||||
Log.e(TAG_UNITY, "Activity class for notifications not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Class.forName(activityClassName);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ClassNotFoundException e) {
|
||||
Log.e(TAG_UNITY, "Failed to find activity class: " + e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isUnityActivity(String name) {
|
||||
return name.endsWith(".UnityPlayerActivity") || name.endsWith(".UnityPlayerGameActivity");
|
||||
}
|
||||
|
||||
protected static Notification.Builder recoverBuilder(Context context, Notification notification) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
Notification.Builder builder = Notification.Builder.recoverBuilder(context, notification);
|
||||
// extras not recovered, transfer manually
|
||||
builder.setExtras(notification.extras);
|
||||
return builder;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG_UNITY, "Failed to recover builder for notification!", e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e(TAG_UNITY, "Failed to recover builder for notification!", e);
|
||||
}
|
||||
|
||||
return recoverBuilderCustom(context, notification);
|
||||
}
|
||||
|
||||
private static Notification.Builder recoverBuilderCustom(Context context, Notification notification) {
|
||||
String channelID = notification.extras.getString(KEY_CHANNEL_ID);
|
||||
Notification.Builder builder = UnityNotificationManager.getNotificationManagerImpl(context).createNotificationBuilder(channelID);
|
||||
UnityNotificationManager.setNotificationIcon(builder, KEY_SMALL_ICON, notification.extras.getString(KEY_SMALL_ICON));
|
||||
String largeIcon = notification.extras.getString(KEY_LARGE_ICON);
|
||||
if (largeIcon != null && !largeIcon.isEmpty())
|
||||
UnityNotificationManager.setNotificationIcon(builder, KEY_LARGE_ICON, largeIcon);
|
||||
builder.setContentTitle(notification.extras.getString(Notification.EXTRA_TITLE));
|
||||
builder.setContentText(notification.extras.getString(Notification.EXTRA_TEXT));
|
||||
builder.setAutoCancel(0 != (notification.flags & Notification.FLAG_AUTO_CANCEL));
|
||||
if (notification.number >= 0)
|
||||
builder.setNumber(notification.number);
|
||||
String bigText = notification.extras.getString(Notification.EXTRA_BIG_TEXT);
|
||||
if (bigText != null)
|
||||
builder.setStyle(new Notification.BigTextStyle().bigText(bigText));
|
||||
|
||||
builder.setWhen(notification.when);
|
||||
String group = notification.getGroup();
|
||||
if (group != null && !group.isEmpty())
|
||||
builder.setGroup(group);
|
||||
builder.setGroupSummary(0 != (notification.flags & Notification.FLAG_GROUP_SUMMARY));
|
||||
String sortKey = notification.getSortKey();
|
||||
if (sortKey != null && !sortKey.isEmpty())
|
||||
builder.setSortKey(sortKey);
|
||||
builder.setShowWhen(notification.extras.getBoolean(Notification.EXTRA_SHOW_WHEN, false));
|
||||
Integer color = UnityNotificationManager.getNotificationColor(notification);
|
||||
if (color != null)
|
||||
UnityNotificationManager.setNotificationColor(builder, color);
|
||||
UnityNotificationManager.setNotificationUsesChronometer(builder, notification.extras.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER, false));
|
||||
UnityNotificationManager.setNotificationGroupAlertBehavior(builder, UnityNotificationManager.getNotificationGroupAlertBehavior(notification));
|
||||
|
||||
builder.getExtras().putInt(KEY_ID, notification.extras.getInt(KEY_ID, 0));
|
||||
builder.getExtras().putLong(KEY_REPEAT_INTERVAL, notification.extras.getLong(KEY_REPEAT_INTERVAL, 0));
|
||||
builder.getExtras().putLong(KEY_FIRE_TIME, notification.extras.getLong(KEY_FIRE_TIME, 0));
|
||||
String intentData = notification.extras.getString(KEY_INTENT_DATA);
|
||||
if (intentData != null && !intentData.isEmpty())
|
||||
builder.getExtras().putString(KEY_INTENT_DATA, intentData);
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Unity.Notifications.Android",
|
||||
"references": [],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Android",
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": []
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac145e6b8c6034cdbadc8c6e26aedbcf
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user