1070 lines
47 KiB
C#
Raw Normal View History

2025-05-18 01:04:31 +08:00
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Android;
#if UNITY_2022_2_OR_NEWER
using JniMethodID = System.IntPtr;
using JniFieldID = System.IntPtr;
#else
using JniMethodID = System.String;
using JniFieldID = System.String;
#endif
namespace Unity.Notifications.Android
{
/// <summary>
/// Current status of a scheduled notification, can be queried using CheckScheduledNotificationStatus.
/// </summary>
public enum NotificationStatus
{
/// <summary>
/// Status of a specified notification cannot be determined. This is only supported on Android Marshmallow (6.0) and above.
/// </summary>
Unavailable = -1,
/// <summary>
/// A notification with a specified id could not be found.
/// </summary>
Unknown = 0,
/// <summary>
/// A notification with a specified id is scheduled but not yet delivered.
/// </summary>
Scheduled = 1,
/// <summary>
/// A notification with a specified id was already delivered.
/// </summary>
Delivered = 2,
}
struct NotificationManagerJni
{
private AndroidJavaClass klass;
private AndroidJavaObject self;
public AndroidJavaObject KEY_FIRE_TIME;
public AndroidJavaObject KEY_ID;
public AndroidJavaObject KEY_INTENT_DATA;
public AndroidJavaObject KEY_LARGE_ICON;
public AndroidJavaObject KEY_REPEAT_INTERVAL;
public AndroidJavaObject KEY_NOTIFICATION;
public AndroidJavaObject KEY_SMALL_ICON;
public AndroidJavaObject KEY_SHOW_IN_FOREGROUND;
private JniMethodID getNotificationFromIntent;
private JniMethodID setNotificationIcon;
private JniMethodID setNotificationColor;
private JniMethodID getNotificationColor;
private JniMethodID setNotificationUsesChronometer;
private JniMethodID setNotificationGroupAlertBehavior;
private JniMethodID getNotificationGroupAlertBehavior;
private JniMethodID getNotificationChannelId;
private JniMethodID scheduleNotification;
private JniMethodID createNotificationBuilder;
public NotificationManagerJni(AndroidJavaClass clazz, AndroidJavaObject obj)
{
klass = clazz;
self = obj;
getNotificationFromIntent = default;
setNotificationIcon = default;
setNotificationColor = default;
getNotificationColor = default;
setNotificationUsesChronometer = default;
setNotificationGroupAlertBehavior = default;
getNotificationGroupAlertBehavior = default;
getNotificationChannelId = default;
scheduleNotification = default;
createNotificationBuilder = default;
#if UNITY_ANDROID && !UNITY_EDITOR
KEY_FIRE_TIME = clazz.GetStatic<AndroidJavaObject>("KEY_FIRE_TIME");
KEY_ID = clazz.GetStatic<AndroidJavaObject>("KEY_ID");
KEY_INTENT_DATA = clazz.GetStatic<AndroidJavaObject>("KEY_INTENT_DATA");
KEY_LARGE_ICON = clazz.GetStatic<AndroidJavaObject>("KEY_LARGE_ICON");
KEY_REPEAT_INTERVAL = clazz.GetStatic<AndroidJavaObject>("KEY_REPEAT_INTERVAL");
KEY_NOTIFICATION = clazz.GetStatic<AndroidJavaObject>("KEY_NOTIFICATION");
KEY_SMALL_ICON = clazz.GetStatic<AndroidJavaObject>("KEY_SMALL_ICON");
KEY_SHOW_IN_FOREGROUND = clazz.GetStatic<AndroidJavaObject>("KEY_SHOW_IN_FOREGROUND");
CollectMethods(clazz);
#else
KEY_FIRE_TIME = null;
KEY_ID = null;
KEY_INTENT_DATA = null;
KEY_LARGE_ICON = null;
KEY_REPEAT_INTERVAL = null;
KEY_NOTIFICATION = null;
KEY_SMALL_ICON = null;
KEY_SHOW_IN_FOREGROUND = null;
#endif
}
void CollectMethods(AndroidJavaClass clazz)
{
getNotificationFromIntent = JniApi.FindMethod(clazz, "getNotificationFromIntent", "(Landroid/content/Intent;)Landroid/app/Notification;", false);
setNotificationIcon = JniApi.FindMethod(clazz, "setNotificationIcon", "(Landroid/app/Notification$Builder;Ljava/lang/String;Ljava/lang/String;)V", true);
setNotificationColor = JniApi.FindMethod(clazz, "setNotificationColor", "(Landroid/app/Notification$Builder;I)V", true);
getNotificationColor = JniApi.FindMethod(clazz, "getNotificationColor", "(Landroid/app/Notification;)Ljava/lang/Integer;", true);
setNotificationUsesChronometer = JniApi.FindMethod(clazz, "setNotificationUsesChronometer", "(Landroid/app/Notification$Builder;Z)V", true);
setNotificationGroupAlertBehavior = JniApi.FindMethod(clazz, "setNotificationGroupAlertBehavior", "(Landroid/app/Notification$Builder;I)V", true);
getNotificationGroupAlertBehavior = JniApi.FindMethod(clazz, "getNotificationGroupAlertBehavior", "(Landroid/app/Notification;)I", true);
getNotificationChannelId = JniApi.FindMethod(clazz, "getNotificationChannelId", "(Landroid/app/Notification;)Ljava/lang/String;", true);
scheduleNotification = JniApi.FindMethod(clazz, "scheduleNotification", "(Landroid/app/Notification$Builder;Z)I", false);
createNotificationBuilder = JniApi.FindMethod(clazz, "createNotificationBuilder", "(Ljava/lang/String;)Landroid/app/Notification$Builder;", false);
}
public AndroidJavaObject GetNotificationFromIntent(AndroidJavaObject intent)
{
return self.Call<AndroidJavaObject>(getNotificationFromIntent, intent);
}
public void SetNotificationIcon(AndroidJavaObject builder, AndroidJavaObject keyName, string icon)
{
klass.CallStatic(setNotificationIcon, builder, keyName, icon);
}
public void SetNotificationColor(AndroidJavaObject builder, int color)
{
klass.CallStatic(setNotificationColor, builder, color);
}
public Color? GetNotificationColor(AndroidJavaObject notification)
{
using (var color = klass.CallStatic<AndroidJavaObject>(getNotificationColor, notification))
{
if (color == null)
return null;
#if UNITY_2022_2_OR_NEWER
int val;
AndroidJNIHelper.Unbox(color.GetRawObject(), out val);
return val.ToColor();
#else
return color.Call<int>("intValue").ToColor();
#endif
}
}
public void SetNotificationUsesChronometer(AndroidJavaObject builder, bool usesStopwatch)
{
klass.CallStatic(setNotificationUsesChronometer, builder, usesStopwatch);
}
public void SetNotificationGroupAlertBehavior(AndroidJavaObject builder, int groupAlertBehaviour)
{
klass.CallStatic(setNotificationGroupAlertBehavior, builder, groupAlertBehaviour);
}
public int GetNotificationGroupAlertBehavior(AndroidJavaObject notification)
{
return klass.CallStatic<int>(getNotificationGroupAlertBehavior, notification);
}
public string GetNotificationChannelId(AndroidJavaObject notification)
{
return klass.CallStatic<string>(getNotificationChannelId, notification);
}
public void RegisterNotificationChannel(AndroidNotificationChannel channel)
{
self.Call("registerNotificationChannel",
channel.Id,
channel.Name,
(int)channel.Importance,
channel.Description,
channel.EnableLights,
channel.EnableVibration,
channel.CanBypassDnd,
channel.CanShowBadge,
channel.VibrationPattern,
(int)channel.LockScreenVisibility
);
}
public AndroidJavaObject[] GetNotificationChannels()
{
return self.Call<AndroidJavaObject[]>("getNotificationChannels");
}
public void DeleteNotificationChannel(string channelId)
{
self.Call("deleteNotificationChannel", channelId);
}
public int ScheduleNotification(AndroidJavaObject notificationBuilder, bool customized)
{
return self.Call<int>(scheduleNotification, notificationBuilder, customized);
}
public bool CheckIfPendingNotificationIsRegistered(int id)
{
return self.Call<bool>("checkIfPendingNotificationIsRegistered", id);
}
public void CancelPendingNotification(int id)
{
self.Call("cancelPendingNotification", id);
}
public void CancelDisplayedNotification(int id)
{
self.Call("cancelDisplayedNotification", id);
}
public void CancelAllPendingNotificationIntents()
{
self.Call("cancelAllPendingNotificationIntents");
}
public void CancelAllNotifications()
{
self.Call("cancelAllNotifications");
}
public int CheckNotificationStatus(int id)
{
return self.Call<int>("checkNotificationStatus", id);
}
public void ShowNotificationSettings(string channelId)
{
self.Call("showNotificationSettings", channelId);
}
public AndroidJavaObject CreateNotificationBuilder(String channelId)
{
return self.Call<AndroidJavaObject>(createNotificationBuilder, channelId);
}
}
struct NotificationJni
{
public AndroidJavaObject EXTRA_TITLE;
public AndroidJavaObject EXTRA_TEXT;
public AndroidJavaObject EXTRA_SHOW_CHRONOMETER;
public AndroidJavaObject EXTRA_BIG_TEXT;
public AndroidJavaObject EXTRA_SHOW_WHEN;
public int FLAG_AUTO_CANCEL;
public int FLAG_GROUP_SUMMARY;
JniMethodID getGroup;
JniMethodID getSortKey;
JniFieldID extras;
JniFieldID flags;
JniFieldID number;
JniFieldID when;
public void CollectJni()
{
using (var notificationClass = new AndroidJavaClass("android.app.Notification"))
{
CollectConstants(notificationClass);
CollectMethods(notificationClass);
CollectFields(notificationClass);
}
}
void CollectConstants(AndroidJavaClass clazz)
{
EXTRA_TITLE = clazz.GetStatic<AndroidJavaObject>("EXTRA_TITLE");
EXTRA_TEXT = clazz.GetStatic<AndroidJavaObject>("EXTRA_TEXT");
EXTRA_SHOW_CHRONOMETER = clazz.GetStatic<AndroidJavaObject>("EXTRA_SHOW_CHRONOMETER");
EXTRA_BIG_TEXT = clazz.GetStatic<AndroidJavaObject>("EXTRA_BIG_TEXT");
EXTRA_SHOW_WHEN = clazz.GetStatic<AndroidJavaObject>("EXTRA_SHOW_WHEN");
FLAG_AUTO_CANCEL = clazz.GetStatic<int>("FLAG_AUTO_CANCEL");
FLAG_GROUP_SUMMARY = clazz.GetStatic<int>("FLAG_GROUP_SUMMARY");
}
void CollectMethods(AndroidJavaClass clazz)
{
getGroup = JniApi.FindMethod(clazz, "getGroup", "()Ljava/lang/String;", false);
getSortKey = JniApi.FindMethod(clazz, "getSortKey", "()Ljava/lang/String;", false);
}
void CollectFields(AndroidJavaClass clazz)
{
extras = JniApi.FindField(clazz, "extras", "Landroid/os/Bundle;", false);
flags = JniApi.FindField(clazz, "flags", "I", false);
number = JniApi.FindField(clazz, "number", "I", false);
when = JniApi.FindField(clazz, "when", "J", false);
}
public AndroidJavaObject Extras(AndroidJavaObject notification)
{
return notification.Get<AndroidJavaObject>(extras);
}
public int Flags(AndroidJavaObject notification)
{
return notification.Get<int>(flags);
}
public int Number(AndroidJavaObject notification)
{
return notification.Get<int>(number);
}
public string GetGroup(AndroidJavaObject notification)
{
return notification.Call<string>(getGroup);
}
public string GetSortKey(AndroidJavaObject notification)
{
return notification.Call<string>(getSortKey);
}
internal long When(AndroidJavaObject notification)
{
return notification.Get<long>(when);
}
}
struct NotificationBuilderJni
{
JniMethodID getExtras;
JniMethodID setContentTitle;
JniMethodID setContentText;
JniMethodID setAutoCancel;
JniMethodID setNumber;
JniMethodID setStyle;
JniMethodID setWhen;
JniMethodID setGroup;
JniMethodID setGroupSummary;
JniMethodID setSortKey;
JniMethodID setShowWhen;
public void CollectJni()
{
using (var clazz = new AndroidJavaClass("android.app.Notification$Builder"))
{
getExtras = JniApi.FindMethod(clazz, "getExtras", "()Landroid/os/Bundle;", false);
setContentTitle = JniApi.FindMethod(clazz, "setContentTitle", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;", false);
setContentText = JniApi.FindMethod(clazz, "setContentText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;", false);
setAutoCancel = JniApi.FindMethod(clazz, "setAutoCancel", "(Z)Landroid/app/Notification$Builder;", false);
setNumber = JniApi.FindMethod(clazz, "setNumber", "(I)Landroid/app/Notification$Builder;", false);
setStyle = JniApi.FindMethod(clazz, "setStyle", "(Landroid/app/Notification$Style;)Landroid/app/Notification$Builder;", false);
setWhen = JniApi.FindMethod(clazz, "setWhen", "(J)Landroid/app/Notification$Builder;", false);
setGroup = JniApi.FindMethod(clazz, "setGroup", "(Ljava/lang/String;)Landroid/app/Notification$Builder;", false);
setGroupSummary = JniApi.FindMethod(clazz, "setGroupSummary", "(Z)Landroid/app/Notification$Builder;", false);
setSortKey = JniApi.FindMethod(clazz, "setSortKey", "(Ljava/lang/String;)Landroid/app/Notification$Builder;", false);
setShowWhen = JniApi.FindMethod(clazz, "setShowWhen", "(Z)Landroid/app/Notification$Builder;", false);
}
}
public AndroidJavaObject GetExtras(AndroidJavaObject builder)
{
return builder.Call<AndroidJavaObject>(getExtras);
}
public void SetContentTitle(AndroidJavaObject builder, string title)
{
builder.Call<AndroidJavaObject>(setContentTitle, title).Dispose();
}
public void SetContentText(AndroidJavaObject builder, string text)
{
builder.Call<AndroidJavaObject>(setContentText, text).Dispose();
}
public void SetAutoCancel(AndroidJavaObject builder, bool shouldAutoCancel)
{
builder.Call<AndroidJavaObject>(setAutoCancel, shouldAutoCancel).Dispose();
}
public void SetNumber(AndroidJavaObject builder, int number)
{
builder.Call<AndroidJavaObject>(setNumber, number).Dispose();
}
public void SetStyle(AndroidJavaObject builder, AndroidJavaObject style)
{
builder.Call<AndroidJavaObject>(setStyle, style).Dispose();
}
public void SetWhen(AndroidJavaObject builder, long timestamp)
{
builder.Call<AndroidJavaObject>(setWhen, timestamp).Dispose();
}
public void SetGroup(AndroidJavaObject builder, string group)
{
builder.Call<AndroidJavaObject>(setGroup, group).Dispose();
}
public void SetGroupSummary(AndroidJavaObject builder, bool groupSummary)
{
builder.Call<AndroidJavaObject>(setGroupSummary, groupSummary).Dispose();
}
public void SetSortKey(AndroidJavaObject builder, string sortKey)
{
builder.Call<AndroidJavaObject>(setSortKey, sortKey).Dispose();
}
public void SetShowWhen(AndroidJavaObject builder, bool showTimestamp)
{
builder.Call<AndroidJavaObject>(setShowWhen, showTimestamp).Dispose();
}
}
struct BundleJni
{
JniMethodID containsKey;
JniMethodID getBoolean;
JniMethodID getInt;
JniMethodID getLong;
JniMethodID getString;
JniMethodID putBoolean;
JniMethodID putInt;
JniMethodID putLong;
JniMethodID putString;
public void CollectJni()
{
using (var clazz = new AndroidJavaClass("android/os/Bundle"))
{
containsKey = JniApi.FindMethod(clazz, "containsKey", "(Ljava/lang/String;)Z", false);
getBoolean = JniApi.FindMethod(clazz, "getBoolean", "(Ljava/lang/String;Z)Z", false);
getInt = JniApi.FindMethod(clazz, "getInt", "(Ljava/lang/String;I)I", false);
getLong = JniApi.FindMethod(clazz, "getLong", "(Ljava/lang/String;J)J", false);
getString = JniApi.FindMethod(clazz, "getString", "(Ljava/lang/String;)Ljava/lang/String;", false);
putBoolean = JniApi.FindMethod(clazz, "putBoolean", "(Ljava/lang/String;Z)V", false);
putInt = JniApi.FindMethod(clazz, "putInt", "(Ljava/lang/String;I)V", false);
putLong = JniApi.FindMethod(clazz, "putLong", "(Ljava/lang/String;J)V", false);
putString = JniApi.FindMethod(clazz, "putString", "(Ljava/lang/String;Ljava/lang/String;)V", false);
}
}
public bool ContainsKey(AndroidJavaObject bundle, AndroidJavaObject key)
{
return bundle.Call<bool>(containsKey, key);
}
public bool GetBoolean(AndroidJavaObject bundle, AndroidJavaObject key, bool defaultValue)
{
return bundle.Call<bool>(getBoolean, key, defaultValue);
}
public int GetInt(AndroidJavaObject bundle, AndroidJavaObject key, int defaultValue)
{
return bundle.Call<int>(getInt, key, defaultValue);
}
public long GetLong(AndroidJavaObject bundle, AndroidJavaObject key, long defaultValue)
{
return bundle.Call<long>(getLong, key, defaultValue);
}
public string GetString(AndroidJavaObject bundle, AndroidJavaObject key)
{
return bundle.Call<string>(getString, key);
}
public void PutBoolean(AndroidJavaObject bundle, AndroidJavaObject key, bool value)
{
bundle.Call(putBoolean, key, value);
}
public void PutInt(AndroidJavaObject bundle, AndroidJavaObject key, int value)
{
bundle.Call(putInt, key, value);
}
public void PutLong(AndroidJavaObject bundle, AndroidJavaObject key, long value)
{
bundle.Call(putLong, key, value);
}
public void PutString(AndroidJavaObject bundle, AndroidJavaObject key, string value)
{
bundle.Call(putString, key, value);
}
}
struct JniApi
{
public NotificationManagerJni NotificationManager;
public NotificationJni Notification;
public NotificationBuilderJni NotificationBuilder;
public BundleJni Bundle;
public JniApi(AndroidJavaClass notificationManagerClass, AndroidJavaObject notificationManager)
{
NotificationManager = new NotificationManagerJni(notificationManagerClass, notificationManager);
Notification = default;
Notification.CollectJni();
NotificationBuilder = default;
NotificationBuilder.CollectJni();
Bundle = default;
Bundle.CollectJni();
}
public static JniFieldID FindField(AndroidJavaClass clazz, string name, string signature, bool isStatic)
{
#if UNITY_2022_2_OR_NEWER
var field = AndroidJNIHelper.GetFieldID(clazz.GetRawClass(), name, signature, isStatic);
if (field == IntPtr.Zero)
throw new Exception($"Field {name} with signature {signature} not found");
return field;
#else
return name;
#endif
}
public static JniMethodID FindMethod(AndroidJavaClass clazz, string name, string signature, bool isStatic)
{
#if UNITY_2022_2_OR_NEWER
var method = AndroidJNIHelper.GetMethodID(clazz.GetRawClass(), name, signature, isStatic);
if (method == IntPtr.Zero)
throw new Exception($"Method {name} with signature {signature} not found");
return method;
#else
return name;
#endif
}
}
/// <summary>
/// Use the AndroidNotificationCenter to register notification channels and schedule local notifications.
/// </summary>
public class AndroidNotificationCenter
{
private static int API_POST_NOTIFICATIONS_PERMISSION_REQUIRED = 33;
internal static string PERMISSION_POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
/// <summary>
/// A PlayerPrefs key used to save users reply to POST_NOTIFICATIONS request (integer value of the PermissionStatus).
/// </summary>
/// <see cref="PermissionStatus"/>
public static string SETTING_POST_NOTIFICATIONS_PERMISSION = "com.unity.androidnotifications.PostNotificationsPermission";
/// <summary>
/// The delegate type for the notification received callbacks.
/// </summary>
public delegate void NotificationReceivedCallback(AndroidNotificationIntentData data);
/// <summary>
/// Subscribe to this event to receive callbacks whenever a scheduled notification is shown to the user.
/// </summary>
public static event NotificationReceivedCallback OnNotificationReceived = delegate { };
private static AndroidJavaObject s_CurrentActivity;
private static JniApi s_Jni;
private static int s_DeviceApiLevel;
private static int s_TargetApiLevel;
private static bool s_Initialized = false;
/// <summary>
/// Initialize the AndroidNotificationCenter class.
/// Can be safely called multiple times
/// </summary>
/// <returns>True if has been successfully initialized</returns>
public static bool Initialize()
{
if (s_Initialized)
return true;
if (AndroidReceivedNotificationMainThreadDispatcher.GetInstance() == null)
{
var receivedNotificationDispatcher = new GameObject("AndroidReceivedNotificationMainThreadDispatcher");
receivedNotificationDispatcher.AddComponent<AndroidReceivedNotificationMainThreadDispatcher>();
}
#if UNITY_EDITOR || !UNITY_ANDROID
s_CurrentActivity = null;
#elif UNITY_ANDROID
using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
s_CurrentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
var notificationManagerClass = new AndroidJavaClass("com.unity.androidnotifications.UnityNotificationManager");
var notificationManager = notificationManagerClass.CallStatic<AndroidJavaObject>("getNotificationManagerImpl", s_CurrentActivity, new NotificationCallback());
s_Jni = new JniApi(notificationManagerClass, notificationManager);
using (var version = new AndroidJavaClass("android/os/Build$VERSION"))
s_DeviceApiLevel = version.GetStatic<int>("SDK_INT");
s_TargetApiLevel = notificationManager.Call<int>("getTargetSdk");
s_Initialized = true;
#endif
return s_Initialized;
}
internal static void SetPostPermissionSetting(PermissionStatus status)
{
PlayerPrefs.SetInt(SETTING_POST_NOTIFICATIONS_PERMISSION, (int)status);
}
/// <summary>
/// Has user given permission to post notifications.
/// Before Android 13 (API 33) no permission is required.
/// </summary>
public static PermissionStatus UserPermissionToPost
{
get
{
if (!Initialize())
return PermissionStatus.Denied;
if (s_DeviceApiLevel < API_POST_NOTIFICATIONS_PERMISSION_REQUIRED)
return PermissionStatus.Allowed;
var permissionStatus = (PermissionStatus)PlayerPrefs.GetInt(SETTING_POST_NOTIFICATIONS_PERMISSION, (int)PermissionStatus.NotRequested);
var allowed = Permission.HasUserAuthorizedPermission(PERMISSION_POST_NOTIFICATIONS);
if (allowed)
{
if (permissionStatus != PermissionStatus.Allowed)
SetPostPermissionSetting(PermissionStatus.Allowed);
return PermissionStatus.Allowed;
}
switch (permissionStatus)
{
case PermissionStatus.NotRequested:
break;
case PermissionStatus.Allowed:
permissionStatus = PermissionStatus.Denied;
SetPostPermissionSetting(permissionStatus);
break;
case PermissionStatus.DeniedDontAskAgain: // no longer used, revert to Denied
permissionStatus = PermissionStatus.Denied;
break;
}
return permissionStatus;
}
}
/// <summary>
/// Creates a notification channel that notifications can be posted to.
/// Notification channel settings can be changed by users on devices running Android 8.0 and above.
/// On older Android versions settings set on the notification channel struct will still be applied to the notification
/// if they are supported to by the Android version the app is running on.
/// </summary>
/// <param name="channel">Channel parameters</param>
/// <remarks>
/// When a channel is deleted and recreated, all of the previous settings are restored. In order to change any settings
/// besides the name or description an entirely new channel (with a different channel ID) must be created.
/// </remarks>
public static void RegisterNotificationChannel(AndroidNotificationChannel channel)
{
if (!Initialize())
return;
if (string.IsNullOrEmpty(channel.Id))
{
throw new Exception("Cannot register notification channel, the channel ID is not specified.");
}
if (string.IsNullOrEmpty(channel.Name))
{
throw new Exception(string.Format("Cannot register notification channel: {0} , the channel Name is not set.", channel.Id));
}
if (string.IsNullOrEmpty(channel.Description))
{
throw new Exception(string.Format("Cannot register notification channel: {0} , the channel Description is not set.", channel.Id));
}
s_Jni.NotificationManager.RegisterNotificationChannel(channel);
}
/// <summary>
/// Returns the notification channel with the specified id.
/// The notification channel struct fields might not be identical to the channel struct used to initially register the channel if they were changed by the user.
/// </summary>
/// <param name="channelId">ID of the channel to retrieve</param>
/// <returns>Channel with given ID or empty struct if such channel does not exist</returns>
public static AndroidNotificationChannel GetNotificationChannel(string channelId)
{
return GetNotificationChannels().SingleOrDefault(channel => channel.Id == channelId);
}
/// <summary>
/// Returns all notification channels that were created by the app.
/// </summary>
/// <returns>All existing channels</returns>
public static AndroidNotificationChannel[] GetNotificationChannels()
{
if (!Initialize())
return new AndroidNotificationChannel[0];
var androidChannels = s_Jni.NotificationManager.GetNotificationChannels();
var channels = new AndroidNotificationChannel[androidChannels == null ? 0 : androidChannels.Length];
for (int i = 0; i < channels.Length; ++i)
{
var channel = androidChannels[i];
var ch = new AndroidNotificationChannel();
ch.Id = channel.Get<string>("id");
ch.Name = channel.Get<string>("name");
ch.Importance = channel.Get<int>("importance").ToImportance();
ch.Description = channel.Get<string>("description");
ch.EnableLights = channel.Get<bool>("enableLights");
ch.EnableVibration = channel.Get<bool>("enableVibration");
ch.CanBypassDnd = channel.Get<bool>("canBypassDnd");
ch.CanShowBadge = channel.Get<bool>("canShowBadge");
ch.VibrationPattern = channel.Get<long[]>("vibrationPattern");
ch.LockScreenVisibility = channel.Get<int>("lockscreenVisibility").ToLockScreenVisibility();
channels[i] = ch;
}
return channels;
}
/// <summary>
/// Delete the specified notification channel.
/// </summary>
/// <param name="channelId">ID of the channel to delete</param>
public static void DeleteNotificationChannel(string channelId)
{
if (Initialize())
s_Jni.NotificationManager.DeleteNotificationChannel(channelId);
}
/// <summary>
/// Schedule a notification which will be shown at the time specified in the notification struct.
/// The returned id can later be used to update the notification before it's triggered, it's current status can be tracked using CheckScheduledNotificationStatus.
/// </summary>
/// <param name="notification">Data for the notification</param>
/// <param name="channelId">ID of the channel to send notification to</param>
/// <returns>The generated ID for the notification</returns>
public static int SendNotification(AndroidNotification notification, string channelId)
{
if (!Initialize())
return -1;
using (var builder = CreateNotificationBuilder(notification, channelId))
return ScheduleNotification(builder, false);
}
/// <summary>
/// Schedule a notification which will be shown at the time specified in the notification struct.
/// The specified id can later be used to update the notification before it's triggered, it's current status can be tracked using CheckScheduledNotificationStatus.
/// </summary>
/// <param name="notification">Data for the notification</param>
/// <param name="channelId">ID of the channel to send notification to</param>
/// <param name="id">A unique ID for the notification</param>
public static void SendNotificationWithExplicitID(AndroidNotification notification, string channelId, int id)
{
if (Initialize())
using (var builder = CreateNotificationBuilder(id, notification, channelId))
ScheduleNotification(builder, false);
}
/// <summary>
/// Schedule a notification created using the provided Notification.Builder object.
/// Notification builder should be created by calling CreateNotificationBuilder.
/// </summary>
public static void SendNotification(AndroidJavaObject notificationBuilder)
{
if (Initialize())
ScheduleNotification(notificationBuilder, true);
}
/// <summary>
/// Schedule a notification created using the provided Notification.Builder object.
/// Notification builder should be created by calling CreateNotificationBuilder.
/// Stores the notification id to the second argument
/// </summary>
public static void SendNotification(AndroidJavaObject notificationBuilder, out int id)
{
id = -1;
if (Initialize())
id = ScheduleNotification(notificationBuilder, true);
}
static int ScheduleNotification(AndroidJavaObject notificationBuilder, bool customized)
{
return s_Jni.NotificationManager.ScheduleNotification(notificationBuilder, customized);
}
/// <summary>
/// Update an already scheduled notification.
/// If a notification with the specified id was already scheduled it will be overridden with the information from the passed notification struct.
/// </summary>
/// <param name="id">ID of the notification to update</param>
/// <param name="notification">Data for the notification</param>
/// <param name="channelId">ID of the channel to send notification to</param>
public static void UpdateScheduledNotification(int id, AndroidNotification notification, string channelId)
{
if (!Initialize())
return;
if (s_Jni.NotificationManager.CheckIfPendingNotificationIsRegistered(id))
{
using (var builder = CreateNotificationBuilder(id, notification, channelId))
{
SendNotification(builder);
}
}
}
/// <summary>
/// Cancel a scheduled or previously shown notification.
/// The notification will no longer be displayed on it's scheduled time. If it's already delivered it will be removed from the status bar.
/// </summary>
/// <param name="id">ID of the notification to cancel</param>
public static void CancelNotification(int id)
{
if (!Initialize())
return;
CancelScheduledNotification(id);
CancelDisplayedNotification(id);
}
/// <summary>
/// Cancel a scheduled notification.
/// The notification will no longer be displayed on it's scheduled time. It it will not be removed from the status bar if it's already delivered.
/// </summary>
/// <param name="id">ID of the notification to cancel</param>
public static void CancelScheduledNotification(int id)
{
if (Initialize())
s_Jni.NotificationManager.CancelPendingNotification(id);
}
/// <summary>
/// Cancel a previously shown notification.
/// The notification will be removed from the status bar.
/// </summary>
/// <param name="id">ID of the notification to cancel</param>
public static void CancelDisplayedNotification(int id)
{
if (Initialize())
s_Jni.NotificationManager.CancelDisplayedNotification(id);
}
/// <summary>
/// Cancel all notifications scheduled or previously shown by the app.
/// All scheduled notifications will be canceled. All notifications shown by the app will be removed from the status bar.
/// </summary>
public static void CancelAllNotifications()
{
if (!Initialize())
return;
CancelAllScheduledNotifications();
CancelAllDisplayedNotifications();
}
/// <summary>
/// Cancel all notifications scheduled by the app.
/// All scheduled notifications will be canceled. Notifications will not be removed from the status bar if they are already shown.
/// </summary>
public static void CancelAllScheduledNotifications()
{
if (Initialize())
s_Jni.NotificationManager.CancelAllPendingNotificationIntents();
}
/// <summary>
/// Cancel all previously shown notifications.
/// All notifications shown by the app will be removed from the status bar. All scheduled notifications will still be shown on their scheduled time.
/// </summary>
public static void CancelAllDisplayedNotifications()
{
if (Initialize())
s_Jni.NotificationManager.CancelAllNotifications();
}
/// <summary>
/// Return the status of a scheduled notification.
/// </summary>
/// <param name="id">ID of the notification to check</param>
/// <returns>The status of the notification</returns>
public static NotificationStatus CheckScheduledNotificationStatus(int id)
{
if (!Initialize())
return NotificationStatus.Unavailable;
var status = s_Jni.NotificationManager.CheckNotificationStatus(id);
return (NotificationStatus)status;
}
/// <summary>
/// Allows retrieving the notification used to open the app. You can save arbitrary string data in the 'AndroidNotification.IntentData' field.
/// </summary>
/// <returns>
/// Returns the AndroidNotification used to open the app, returns null if the app was not opened with a notification.
/// </returns>
public static AndroidNotificationIntentData GetLastNotificationIntent()
{
if (!Initialize())
return null;
var intent = s_CurrentActivity.Call<AndroidJavaObject>("getIntent");
var notification = s_Jni.NotificationManager.GetNotificationFromIntent(intent);
if (notification == null)
return null;
return GetNotificationData(notification);
}
/// <summary>
/// Opens settings.
/// On Android versions lower than 8.0 opens settings for the application.
/// On Android 8.0 and later opens notification settings for the specified channel, or for the application, if channelId is null.
/// Note, that opening settings will suspend the application and switch to settings app.
/// </summary>
/// <param name="channelId">ID for the channel to open or null to open notification settings for the application.</param>
public static void OpenNotificationSettings(string channelId = null)
{
if (!Initialize())
return;
s_Jni.NotificationManager.ShowNotificationSettings(channelId);
}
/// <summary>
/// Create Notification.Builder.
/// Will automatically generate the ID for notification.
/// <see cref="CreateNotificationBuilder(int, AndroidNotification, string)"/>
/// </summary>
public static AndroidJavaObject CreateNotificationBuilder(AndroidNotification notification, string channelId)
{
AndroidJavaObject builder, extras;
CreateNotificationBuilder(notification, channelId, out builder, out extras);
if (extras != null)
extras.Dispose();
return builder;
}
/// <summary>
/// Create Notification.Builder object on Java side using privided AndroidNotification.
/// </summary>
/// <param name="id">ID for the notification</param>
/// <param name="notification">Struct with notification data</param>
/// <param name="channelId">Channel id</param>
/// <returns>A proxy object for created Notification.Builder</returns>
public static AndroidJavaObject CreateNotificationBuilder(int id, AndroidNotification notification, string channelId)
{
AndroidJavaObject builder, extras;
CreateNotificationBuilder(notification, channelId, out builder, out extras);
if (extras != null)
{
s_Jni.Bundle.PutInt(extras, s_Jni.NotificationManager.KEY_ID, id);
extras.Dispose();
}
return builder;
}
static void CreateNotificationBuilder(AndroidNotification notification, string channelId, out AndroidJavaObject notificationBuilder, out AndroidJavaObject extras)
{
if (!Initialize())
{
notificationBuilder = extras = null;
return;
}
long fireTime = notification.FireTime.ToLong();
if (fireTime < 0L)
{
Debug.LogError("Failed to schedule notification, it did not contain a valid FireTime");
}
// NOTE: JNI calls are expensive, so we avoid calls that set something that is also a default
notificationBuilder = s_Jni.NotificationManager.CreateNotificationBuilder(channelId);
s_Jni.NotificationManager.SetNotificationIcon(notificationBuilder, s_Jni.NotificationManager.KEY_SMALL_ICON, notification.SmallIcon);
if (!string.IsNullOrEmpty(notification.LargeIcon))
s_Jni.NotificationManager.SetNotificationIcon(notificationBuilder, s_Jni.NotificationManager.KEY_LARGE_ICON, notification.LargeIcon);
if (!string.IsNullOrEmpty(notification.Title))
s_Jni.NotificationBuilder.SetContentTitle(notificationBuilder, notification.Title);
if (!string.IsNullOrEmpty(notification.Text))
s_Jni.NotificationBuilder.SetContentText(notificationBuilder, notification.Text);
if (notification.ShouldAutoCancel)
s_Jni.NotificationBuilder.SetAutoCancel(notificationBuilder, notification.ShouldAutoCancel);
if (notification.Number >= 0)
s_Jni.NotificationBuilder.SetNumber(notificationBuilder, notification.Number);
if (notification.Style == NotificationStyle.BigTextStyle)
{
using (var style = new AndroidJavaObject("android.app.Notification$BigTextStyle"))
{
style.Call<AndroidJavaObject>("bigText", notification.Text).Dispose();
s_Jni.NotificationBuilder.SetStyle(notificationBuilder, style);
}
}
long timestampValue = notification.ShowCustomTimestamp ? notification.CustomTimestamp.ToLong() : fireTime;
s_Jni.NotificationBuilder.SetWhen(notificationBuilder, timestampValue);
if (!string.IsNullOrEmpty(notification.Group))
s_Jni.NotificationBuilder.SetGroup(notificationBuilder, notification.Group);
if (notification.GroupSummary)
s_Jni.NotificationBuilder.SetGroupSummary(notificationBuilder, notification.GroupSummary);
if (!string.IsNullOrEmpty(notification.SortKey))
s_Jni.NotificationBuilder.SetSortKey(notificationBuilder, notification.SortKey);
if (notification.ShowTimestamp)
s_Jni.NotificationBuilder.SetShowWhen(notificationBuilder, notification.ShowTimestamp);
int color = notification.Color.ToInt();
if (color != 0)
s_Jni.NotificationManager.SetNotificationColor(notificationBuilder, color);
if (notification.UsesStopwatch)
s_Jni.NotificationManager.SetNotificationUsesChronometer(notificationBuilder, notification.UsesStopwatch);
if (notification.GroupAlertBehaviour != GroupAlertBehaviours.GroupAlertAll) // All is default value
s_Jni.NotificationManager.SetNotificationGroupAlertBehavior(notificationBuilder, (int)notification.GroupAlertBehaviour);
extras = s_Jni.NotificationBuilder.GetExtras(notificationBuilder);
s_Jni.Bundle.PutLong(extras, s_Jni.NotificationManager.KEY_REPEAT_INTERVAL, notification.RepeatInterval.ToLong());
s_Jni.Bundle.PutLong(extras, s_Jni.NotificationManager.KEY_FIRE_TIME, fireTime);
s_Jni.Bundle.PutBoolean(extras, s_Jni.NotificationManager.KEY_SHOW_IN_FOREGROUND, notification.ShowInForeground);
if (!string.IsNullOrEmpty(notification.IntentData))
s_Jni.Bundle.PutString(extras, s_Jni.NotificationManager.KEY_INTENT_DATA, notification.IntentData);
}
internal static AndroidNotificationIntentData GetNotificationData(AndroidJavaObject notificationObj)
{
using (var extras = s_Jni.Notification.Extras(notificationObj))
{
var id = s_Jni.Bundle.GetInt(extras, s_Jni.NotificationManager.KEY_ID, -1);
if (id == -1)
return null;
var channelId = s_Jni.NotificationManager.GetNotificationChannelId(notificationObj);
int flags = s_Jni.Notification.Flags(notificationObj);
var notification = new AndroidNotification();
notification.Title = s_Jni.Bundle.GetString(extras, s_Jni.Notification.EXTRA_TITLE);
notification.Text = s_Jni.Bundle.GetString(extras, s_Jni.Notification.EXTRA_TEXT);
notification.SmallIcon = s_Jni.Bundle.GetString(extras, s_Jni.NotificationManager.KEY_SMALL_ICON);
notification.LargeIcon = s_Jni.Bundle.GetString(extras, s_Jni.NotificationManager.KEY_LARGE_ICON);
notification.ShouldAutoCancel = 0 != (flags & s_Jni.Notification.FLAG_AUTO_CANCEL);
notification.UsesStopwatch = s_Jni.Bundle.GetBoolean(extras, s_Jni.Notification.EXTRA_SHOW_CHRONOMETER, false);
notification.FireTime = s_Jni.Bundle.GetLong(extras, s_Jni.NotificationManager.KEY_FIRE_TIME, -1L).ToDatetime();
notification.RepeatInterval = s_Jni.Bundle.GetLong(extras, s_Jni.NotificationManager.KEY_REPEAT_INTERVAL, -1L).ToTimeSpan();
notification.ShowInForeground = s_Jni.Bundle.GetBoolean(extras, s_Jni.NotificationManager.KEY_SHOW_IN_FOREGROUND, true);
if (s_Jni.Bundle.ContainsKey(extras, s_Jni.Notification.EXTRA_BIG_TEXT))
notification.Style = NotificationStyle.BigTextStyle;
else
notification.Style = NotificationStyle.None;
notification.Color = s_Jni.NotificationManager.GetNotificationColor(notificationObj);
notification.Number = s_Jni.Notification.Number(notificationObj);
notification.IntentData = s_Jni.Bundle.GetString(extras, s_Jni.NotificationManager.KEY_INTENT_DATA);
notification.Group = s_Jni.Notification.GetGroup(notificationObj);
notification.GroupSummary = 0 != (flags & s_Jni.Notification.FLAG_GROUP_SUMMARY);
notification.SortKey = s_Jni.Notification.GetSortKey(notificationObj);
notification.GroupAlertBehaviour = s_Jni.NotificationManager.GetNotificationGroupAlertBehavior(notificationObj).ToGroupAlertBehaviours();
var showTimestamp = s_Jni.Bundle.GetBoolean(extras, s_Jni.Notification.EXTRA_SHOW_WHEN, false);
notification.ShowTimestamp = showTimestamp;
if (showTimestamp)
notification.CustomTimestamp = s_Jni.Notification.When(notificationObj).ToDatetime();
var data = new AndroidNotificationIntentData(id, channelId, notification);
data.NativeNotification = notificationObj;
return data;
}
}
internal static void ReceivedNotificationCallback(AndroidJavaObject notification)
{
var data = GetNotificationData(notification);
OnNotificationReceived(data);
}
}
}