fix:1、更换sdk
This commit is contained in:
@@ -98,6 +98,14 @@ public class TCJPLYRM1xJTSDKDemo : MonoBehaviour
|
||||
});
|
||||
}
|
||||
|
||||
public void ShowH5WebView()
|
||||
{
|
||||
TCJPLYRM1xJTSDKManager.Instance.H5.ShowWebView((() =>
|
||||
{
|
||||
Debug.Log("H5 close");
|
||||
}));
|
||||
}
|
||||
|
||||
public void ShowH5(RectTransform rectTransform)
|
||||
{
|
||||
TCJPLYRM1xJTSDKManager.Instance.H5.ShowH5(rectTransform);
|
||||
@@ -150,25 +158,26 @@ public class TCJPLYRM1xJTSDKDemo : MonoBehaviour
|
||||
private int payIndex = 0;
|
||||
public void CreateWithDrawOrder()
|
||||
{
|
||||
string taxNo = "";
|
||||
string taxNo = "99999999999";
|
||||
string payAccount = "tom@gmail.com";
|
||||
string accountType = "E";
|
||||
PaymentTypeCode payCode = PaymentTypeCode.GOPAY;
|
||||
//GOPAY DANA 收款账号需要为电话号码
|
||||
if (payIndex == 1 || payIndex == 0)
|
||||
{
|
||||
payCode = payIndex == 1 ? PaymentTypeCode.DANA : payCode;
|
||||
payAccount = "0881234567890";
|
||||
accountType = "P";
|
||||
}
|
||||
if (payIndex == 2)
|
||||
{
|
||||
// PIX 需要填写税号
|
||||
payCode = PaymentTypeCode.PIX;
|
||||
taxNo = "99999999999";
|
||||
}
|
||||
if (payIndex == 3) payCode = PaymentTypeCode.MERCADOPAGO;
|
||||
TCJPLYRM1xJTSDKManager.Instance.CreateWithDrawOrder(withDrawSceneId, payCode, payAccount, accountType, "testName", taxNo, b =>
|
||||
string payeeName = "testName";
|
||||
PaymentTypeCode payCode = PaymentTypeCode.PAYPAL;
|
||||
////GOPAY DANA 收款账号需要为电话号码
|
||||
//if (payIndex == 1 || payIndex == 0)
|
||||
//{
|
||||
// payCode = payIndex == 1 ? PaymentTypeCode.DANA : payCode;
|
||||
// payAccount = "0881234567890";
|
||||
// accountType = "P";
|
||||
//}
|
||||
//if (payIndex == 2)
|
||||
//{
|
||||
// // PIX 需要填写税号
|
||||
// payCode = PaymentTypeCode.PIX;
|
||||
// taxNo = "99999999999";
|
||||
//}
|
||||
//if (payIndex == 3) payCode = PaymentTypeCode.MERCADOPAGO;
|
||||
TCJPLYRM1xJTSDKManager.Instance.CreateWithDrawOrder(withDrawSceneId, payCode, payAccount, accountType, payeeName, taxNo, b =>
|
||||
{
|
||||
Debug.Log($"CreateWithDrawOrder result : {b}");
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -36,7 +36,7 @@ public static void ShowWin()
|
||||
|
||||
#region topon
|
||||
|
||||
_view.loadPluginData();
|
||||
// _view.loadPluginData();
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -55,29 +55,29 @@ private void OnGUI()
|
||||
}
|
||||
|
||||
#region Topon
|
||||
private AnyThink.Scripts.IntegrationManager.Editor.ATEditorCoroutine loadDataCoroutine;
|
||||
private AnyThink.Scripts.IntegrationManager.Editor.PluginData pluginData;
|
||||
private bool pluginDataLoadFailed;
|
||||
private void loadPluginData()
|
||||
{
|
||||
if (loadDataCoroutine != null)
|
||||
{
|
||||
loadDataCoroutine.Stop();
|
||||
}
|
||||
loadDataCoroutine = AnyThink.Scripts.IntegrationManager.Editor.ATEditorCoroutine.startCoroutine(AnyThink.Scripts.IntegrationManager.Editor.ATIntegrationManager.Instance.loadPluginData(data =>
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
pluginDataLoadFailed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ATLog.log("loadNetworksData() >>> pluginData: " + data);
|
||||
pluginData = data;
|
||||
pluginDataLoadFailed = false;
|
||||
}
|
||||
}));
|
||||
}
|
||||
// private AnyThink.Scripts.IntegrationManager.Editor.ATEditorCoroutine loadDataCoroutine;
|
||||
// private AnyThink.Scripts.IntegrationManager.Editor.PluginData pluginData;
|
||||
// private bool pluginDataLoadFailed;
|
||||
// private void loadPluginData()
|
||||
// {
|
||||
// if (loadDataCoroutine != null)
|
||||
// {
|
||||
// loadDataCoroutine.Stop();
|
||||
// }
|
||||
// loadDataCoroutine = AnyThink.Scripts.IntegrationManager.Editor.ATEditorCoroutine.startCoroutine(AnyThink.Scripts.IntegrationManager.Editor.ATIntegrationManager.Instance.loadPluginData(data =>
|
||||
// {
|
||||
// if (data == null)
|
||||
// {
|
||||
// pluginDataLoadFailed = true;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// ATLog.log("loadNetworksData() >>> pluginData: " + data);
|
||||
// pluginData = data;
|
||||
// pluginDataLoadFailed = false;
|
||||
// }
|
||||
// }));
|
||||
// }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -102,47 +102,47 @@ private void loadPluginData()
|
||||
|
||||
#region MAX
|
||||
|
||||
// EditorGUILayout.BeginVertical("frameBox");
|
||||
// TextLabel("MAX", Color.cyan);
|
||||
// SDKConfig.Instance.maxAppKey = DrawTextField("MaxSdkKey", SDKConfig.Instance.maxAppKey);
|
||||
// SDKConfig.Instance.videoUnitId = DrawTextField("激励视频 ID", SDKConfig.Instance.videoUnitId);
|
||||
// SDKConfig.Instance.interUnitId = DrawTextField("插屏 ID", SDKConfig.Instance.interUnitId);
|
||||
// SDKConfig.Instance.bannerUnitId = DrawTextField("Banner ID", SDKConfig.Instance.bannerUnitId);
|
||||
// SDKConfig.Instance.splashUnitId = DrawTextField("开屏 ID", SDKConfig.Instance.splashUnitId);
|
||||
// EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.BeginVertical("frameBox");
|
||||
TextLabel("MAX", Color.cyan);
|
||||
SDKConfig.Instance.maxAppKey = DrawTextField("MaxSdkKey", SDKConfig.Instance.maxAppKey);
|
||||
SDKConfig.Instance.videoUnitId = DrawTextField("激励视频 ID", SDKConfig.Instance.videoUnitId);
|
||||
SDKConfig.Instance.interUnitId = DrawTextField("插屏 ID", SDKConfig.Instance.interUnitId);
|
||||
SDKConfig.Instance.bannerUnitId = DrawTextField("Banner ID", SDKConfig.Instance.bannerUnitId);
|
||||
SDKConfig.Instance.splashUnitId = DrawTextField("开屏 ID", SDKConfig.Instance.splashUnitId);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
// AppLovinSettings.Instance.SdkKey = SDKConfig.Instance.maxAppKey;
|
||||
AppLovinSettings.Instance.SdkKey = SDKConfig.Instance.maxAppKey;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Topon
|
||||
|
||||
EditorGUILayout.BeginVertical("frameBox");
|
||||
TextLabel("Topon", Color.cyan);
|
||||
SDKConfig.Instance.toponAppId = DrawTextField("AppId", SDKConfig.Instance.toponAppId);
|
||||
SDKConfig.Instance.toponAppkey = DrawTextField("Appkey", SDKConfig.Instance.toponAppkey);
|
||||
SDKConfig.Instance.toponVideoUnitId = DrawTextField("激励视频 ID", SDKConfig.Instance.toponVideoUnitId);
|
||||
SDKConfig.Instance.toponInterUnitId = DrawTextField("插屏 ID", SDKConfig.Instance.toponInterUnitId);
|
||||
EditorGUILayout.EndVertical();
|
||||
//AnyThink.Scripts.IntegrationManager.Editor.ATIntegrationManager.Instance.setAdmobAppidByOs(pluginData, );
|
||||
var integrationManager = AnyThink.Scripts.IntegrationManager.Editor.ATIntegrationManager.Instance;
|
||||
bool isAdmobInstalledForAndroid = integrationManager.isAdmobInstalled(AnyThink.Scripts.IntegrationManager.Editor.ATConfig.OS_ANDROID);
|
||||
bool isAdmobInstalledForIos = integrationManager.isAdmobInstalled(AnyThink.Scripts.IntegrationManager.Editor.ATConfig.OS_IOS);
|
||||
// EditorGUILayout.BeginVertical("frameBox");
|
||||
// TextLabel("Topon", Color.cyan);
|
||||
// SDKConfig.Instance.toponAppId = DrawTextField("AppId", SDKConfig.Instance.toponAppId);
|
||||
// SDKConfig.Instance.toponAppkey = DrawTextField("Appkey", SDKConfig.Instance.toponAppkey);
|
||||
// SDKConfig.Instance.toponVideoUnitId = DrawTextField("激励视频 ID", SDKConfig.Instance.toponVideoUnitId);
|
||||
// SDKConfig.Instance.toponInterUnitId = DrawTextField("插屏 ID", SDKConfig.Instance.toponInterUnitId);
|
||||
// EditorGUILayout.EndVertical();
|
||||
|
||||
if (isAdmobInstalledForAndroid || isAdmobInstalledForIos)
|
||||
{
|
||||
if (isAdmobInstalledForAndroid)
|
||||
{
|
||||
var androidAdmobAppId = "ca-app-pub-3940256099942544~3347511713";
|
||||
integrationManager.setAdmobAppidByOs(pluginData, AnyThink.Scripts.IntegrationManager.Editor.ATConfig.OS_ANDROID, androidAdmobAppId);
|
||||
}
|
||||
if (isAdmobInstalledForIos)
|
||||
{
|
||||
var iosAdmobAppId = "ca-app-pub-3940256099942544~1458002511";
|
||||
integrationManager.setAdmobAppidByOs(pluginData, AnyThink.Scripts.IntegrationManager.Editor.ATConfig.OS_IOS, iosAdmobAppId);
|
||||
}
|
||||
}
|
||||
// var integrationManager = AnyThink.Scripts.IntegrationManager.Editor.ATIntegrationManager.Instance;
|
||||
// bool isAdmobInstalledForAndroid = integrationManager.isAdmobInstalled(AnyThink.Scripts.IntegrationManager.Editor.ATConfig.OS_ANDROID);
|
||||
// bool isAdmobInstalledForIos = integrationManager.isAdmobInstalled(AnyThink.Scripts.IntegrationManager.Editor.ATConfig.OS_IOS);
|
||||
|
||||
// if (isAdmobInstalledForAndroid || isAdmobInstalledForIos)
|
||||
// {
|
||||
// if (isAdmobInstalledForAndroid)
|
||||
// {
|
||||
// var androidAdmobAppId = "ca-app-pub-3940256099942544~3347511713";
|
||||
// integrationManager.setAdmobAppidByOs(pluginData, AnyThink.Scripts.IntegrationManager.Editor.ATConfig.OS_ANDROID, androidAdmobAppId);
|
||||
// }
|
||||
// if (isAdmobInstalledForIos)
|
||||
// {
|
||||
// var iosAdmobAppId = "ca-app-pub-3940256099942544~1458002511";
|
||||
// integrationManager.setAdmobAppidByOs(pluginData, AnyThink.Scripts.IntegrationManager.Editor.ATConfig.OS_IOS, iosAdmobAppId);
|
||||
// }
|
||||
// }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8b85f7165318af4cb1fa04f57525bcd
|
||||
guid: 5f042a50523652d489db5b7afac3ea73
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35a84cea4ac83ae4aa29726155d13fcc
|
||||
guid: d511d2e431dd9704b9872a17b2910f98
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<dependencies>
|
||||
<iosPods>
|
||||
<iosPod name="BigoADS" version="5.1.2"/>
|
||||
<iosPod name="BigoADS" version="5.2.1"/>
|
||||
</iosPods>
|
||||
<androidPackages>
|
||||
<androidPackage spec="com.bigossp:bigo-ads:5.3.0">
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
// ============================================================
|
||||
// IAPDemoUsage.cs
|
||||
// IAP 接入使用示例(MonoBehaviour)
|
||||
//
|
||||
// 将此脚本挂到场景中的任意 GameObject 即可运行示例。
|
||||
// 请将商品 ID 替换为你在 App Store Connect / Google Play Console 中配置的真实 ID。
|
||||
// ============================================================
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Purchasing;
|
||||
|
||||
namespace SDK_IAP
|
||||
{
|
||||
public class IAPDemoUsage : MonoBehaviour
|
||||
{
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// 商品 ID 常量(请替换为真实 ID)
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
private const string PRODUCT_GOLD_100 = "mediumpack_01"; // 消耗品
|
||||
private const string PRODUCT_REMOVE_ADS = "non_ads"; // 非消耗品
|
||||
private const string PRODUCT_VIP_MONTH = "subcra"; // 订阅
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// 生命周期
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 订阅发货事件(持续监听,全局处理发货逻辑)
|
||||
IAPManager.OnDeliver += HandleDeliver;
|
||||
IAPManager.OnInitialized += HandleInitialized;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
InitIAP();
|
||||
//StartCoroutine(Test());
|
||||
}
|
||||
|
||||
//private IEnumerator Test()
|
||||
//{
|
||||
// int index = 0;
|
||||
// while (true)
|
||||
// {
|
||||
// yield return new WaitForSeconds(1f);
|
||||
|
||||
// Analytics.OnTrackEvent?.Invoke($"test{index}", new Dictionary<string, string>());
|
||||
// index++;
|
||||
// }
|
||||
//}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
IAPManager.OnDeliver -= HandleDeliver;
|
||||
IAPManager.OnInitialized -= HandleInitialized;
|
||||
IAPManager.Dispose();
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// 初始化
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
private void InitIAP()
|
||||
{
|
||||
var products = new List<ProductDefine>
|
||||
{
|
||||
// 消耗品 — 金币
|
||||
ProductDefine.Simple(PRODUCT_GOLD_100, ProductType.Consumable),
|
||||
|
||||
// 非消耗品 — 去广告(带平台特定 ID 示例)
|
||||
ProductDefine.Simple(
|
||||
PRODUCT_REMOVE_ADS,
|
||||
ProductType.NonConsumable),
|
||||
|
||||
// 订阅 — VIP 月卡
|
||||
ProductDefine.Simple(PRODUCT_VIP_MONTH, ProductType.Subscription),
|
||||
};
|
||||
|
||||
Debug.Log("[IAP Demo] 开始初始化...");
|
||||
IAPManager.Init(products, OnInitCallback);
|
||||
}
|
||||
|
||||
// Init 完成回调(与 OnInitialized 事件等价,二选一即可)
|
||||
private void OnInitCallback(bool success)
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
Debug.Log("[IAP Demo] 初始化成功!");
|
||||
ShowProductPrices();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[IAP Demo] 初始化失败,请检查网络或 App Store / Google Play 配置。");
|
||||
}
|
||||
}
|
||||
|
||||
// 也可以通过静态事件监听(适合多个 Manager 分散处理)
|
||||
private void HandleInitialized(bool success)
|
||||
{
|
||||
// 此处可做更新 UI 等操作
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// 显示商品价格(初始化成功后调用)
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
private void ShowProductPrices()
|
||||
{
|
||||
var products = IAPManager.GetProducts();
|
||||
foreach (var p in products)
|
||||
{
|
||||
Debug.Log($"[IAP Demo] 商品: {p.definition.id} | 价格: {p.metadata.localizedPriceString}");
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// 购买示例
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>购买 100 金币(消耗品)</summary>
|
||||
public void BuyGold100()
|
||||
{
|
||||
IAPManager.Buy(PRODUCT_GOLD_100, result =>
|
||||
{
|
||||
if (result.success)
|
||||
{
|
||||
Debug.Log($"[IAP Demo] 购买成功: {result.productId} | tid={result.transactionId}");
|
||||
// ⚠️ 消耗品奖励建议存服务器,防止本地数据丢失
|
||||
// GoldManager.Add(100);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[IAP Demo] 购买失败: {result.error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>购买去广告(非消耗品)</summary>
|
||||
public void BuyRemoveAds()
|
||||
{
|
||||
IAPManager.Buy(PRODUCT_REMOVE_ADS, result =>
|
||||
{
|
||||
if (result.success)
|
||||
{
|
||||
Debug.Log("[IAP Demo] 去广告购买成功");
|
||||
// AdsManager.Disable();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[IAP Demo] 购买失败: {result.error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>购买 VIP 月卡(订阅)</summary>
|
||||
public void BuyVipMonth()
|
||||
{
|
||||
IAPManager.Buy(PRODUCT_VIP_MONTH, result =>
|
||||
{
|
||||
if (result.success)
|
||||
{
|
||||
Debug.Log("[IAP Demo] VIP 订阅成功");
|
||||
ShowSubscriptionInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[IAP Demo] 订阅失败: {result.error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// 恢复购买(iOS 界面上必须提供此按钮)
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
public void OnRestoreButtonClicked()
|
||||
{
|
||||
// UI 控制:仅在需要时显示此按钮
|
||||
if (!IAPManager.NeedShowRestoreButton()) return;
|
||||
|
||||
IAPManager.Restore(result =>
|
||||
{
|
||||
if (result.success)
|
||||
Debug.Log("[IAP Demo] 恢复购买流程完成(实际恢复内容通过 OnDeliver 发放)");
|
||||
else
|
||||
Debug.LogWarning($"[IAP Demo] 恢复购买失败: {result.error}");
|
||||
});
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// 权益检查
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
public void CheckRemoveAdsEntitlement()
|
||||
{
|
||||
IAPManager.CheckEntitlement(PRODUCT_REMOVE_ADS, status =>
|
||||
{
|
||||
Debug.Log($"[IAP Demo] 去广告权益状态: {status}");
|
||||
// EntitlementStatus.FullyEntitled = 已购买且有效
|
||||
});
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// 订阅信息查询
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowSubscriptionInfo()
|
||||
{
|
||||
var info = IAPManager.GetSubscriptionInfo(PRODUCT_VIP_MONTH);
|
||||
Debug.Log($"[IAP Demo] VIP 订阅状态:");
|
||||
Debug.Log($" isSubscribed = {info.isSubscribed}");
|
||||
Debug.Log($" isExpired = {info.isExpired}");
|
||||
Debug.Log($" expireDate = {info.expireDate:yyyy-MM-dd HH:mm:ss}");
|
||||
Debug.Log($" isAutoRenewing= {info.isAutoRenewing}");
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────
|
||||
// 全局发货处理(OnDeliver 事件接收)
|
||||
// ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 统一发货处理入口。
|
||||
/// 无论是新购买、补单、还是恢复购买,都会触发此方法。
|
||||
/// ⚠️ 幂等保护已由 DeliverGuardLite 处理,此处无需再判重。
|
||||
/// </summary>
|
||||
private void HandleDeliver(string productId)
|
||||
{
|
||||
Debug.Log($"[IAP Demo] 发货通知: {productId}");
|
||||
|
||||
switch (productId)
|
||||
{
|
||||
case PRODUCT_GOLD_100:
|
||||
Debug.Log("[IAP Demo] 发放 100 金币");
|
||||
// GoldManager.Add(100);
|
||||
break;
|
||||
|
||||
case PRODUCT_REMOVE_ADS:
|
||||
Debug.Log("[IAP Demo] 开启去广告");
|
||||
// AdsManager.Disable();
|
||||
break;
|
||||
|
||||
case PRODUCT_VIP_MONTH:
|
||||
Debug.Log("[IAP Demo] 激活 VIP");
|
||||
// VipManager.Activate();
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.LogWarning($"[IAP Demo] 未知商品 ID: {productId}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,12 +0,0 @@
|
||||
artifacts/**
|
||||
build/**
|
||||
.build_script/**
|
||||
node_modules/**
|
||||
.DS_Store
|
||||
.npmrc
|
||||
!Documentation~
|
||||
!.Documentation
|
||||
npm-debug.log
|
||||
build.sh.meta
|
||||
build.bat.meta
|
||||
.idea/
|
||||
@@ -1,19 +0,0 @@
|
||||
artifacts/**
|
||||
build/**
|
||||
.build_script/**
|
||||
node_modules/**
|
||||
Documentation/ApiDocs/**
|
||||
Documentation~/ApiDocs/**
|
||||
.DS_Store
|
||||
.npmrc
|
||||
.npmignore
|
||||
.gitignore
|
||||
CONTRIBUTING.md
|
||||
CONTRIBUTING.md.meta
|
||||
QAReport.md
|
||||
QAReport.md.meta
|
||||
.gitlab-ci.yml
|
||||
build.sh
|
||||
build.sh.meta
|
||||
build.bat
|
||||
build.bat.meta
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
# Using this package
|
||||
|
||||
This package supports Apple's SKAdNetwork and AppTrackingTransparency frameworks for your made-with-Unity iOS application, by aggregating proper ad network IDs and providing access to relevant Apple developer APIs.
|
||||
|
||||
For more information about iOS 14 technical integration, including this package's support functionality, please see the full [iOS 14 technical documentation](https://unityads.unity3d.com/help/monetization/ios14).
|
||||
|
||||
## Installing the package
|
||||
|
||||
### From the public Github repository
|
||||
|
||||
1. Go to the [public repo page on Github](https://github.com/Unity-Technologies/com.unity.ads.ios-support).
|
||||
1. Download that repo to your hard drive.
|
||||
2. In the Unity Editor, open the [Unity Package Manager](https://docs.unity3d.com/Manual/Packages.html) window.
|
||||
3. Click the **+** button and select **Add package from disk...**.
|
||||
4. Navigate to the location where you downloaded the repo and select the iOS14 Support package (_..\com.unity.ads.ios-support-master\com.unity.ads.ios-support\package.json_) to install the package. If successful, the iOS14 Support package will appear in the package manager list (note that you must have **All packages** selected to view it).
|
||||
|
||||
### From Unity Package Manager
|
||||
|
||||
1. In the Unity Editor, select **Window** > **Package** Manager to open the Package Manager.
|
||||
2. Select the **Advertisement** package from the list, then select the most recent verified version.
|
||||
3. Click the **Install** or **Update** button.
|
||||
|
||||
## Apple developer API extensions
|
||||
|
||||
This package provides access to the following Apple developer APIs:
|
||||
|
||||
```
|
||||
public static void SkAdNetworkUpdateConversionValue(int conversionValue)
|
||||
```
|
||||
This method allows you to [update the attribution conversion value](https://developer.apple.com/documentation/storekit/skadnetwork/3566697-updateconversionvalue?language=objc).
|
||||
|
||||
```
|
||||
SkAdNetworkRegisterAppForNetworkAttribution()
|
||||
```
|
||||
This method allows you to [register for attribution](https://developer.apple.com/documentation/storekit/skadnetwork/2943654-registerappforadnetworkattributi?language=objc).
|
||||
|
||||
```
|
||||
public static void RequestAuthorizationTracking()
|
||||
```
|
||||
This method allows you to [request the user permission dialogue](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/3547037-requesttrackingauthorization).
|
||||
|
||||
```
|
||||
public static AuthorizationTrackingStatus GetAuthorizationTrackingStatus()
|
||||
```
|
||||
This method allows you to check the app tracking transparency (ATT) [authorization status](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/3547038-trackingauthorizationstatus).
|
||||
|
||||
## Sample Project
|
||||
|
||||
You can import a Context Screen sample scene and assets from the Package Manager screen for this package (Unity 2019.1 and newer).
|
||||
|
||||
The SampleProject~ folder included in the Github public repo is a complete Unity project, compatible with Unity 2018.4.33f1 and up.
|
||||
|
||||
This sample provides a fully customizable example of a context screen you could use to give context to users before showing the native App Tracking Transparency dialog.
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"createSeparatePackage": false
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e5be99a7c47f7d45bb7ef41ba7af61f
|
||||
guid: 3907509d068e9394e8294fc2aec86578
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
+374
@@ -0,0 +1,374 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this package will be documented in this file.
|
||||
|
||||
## [2.3.2] - 2024-02-09
|
||||
|
||||
### Fixes:
|
||||
- [Android] - Remove excessive permissions and metadata from default manifest (auto-add them based on settings).
|
||||
- [Android] - Fixed notification status reporting after dismissing notification and re-launching the app.
|
||||
- [iOS] - Fixed a possible crash when sending push notifications with attachment (like image).
|
||||
|
||||
## [2.3.1] - 2023-12-11
|
||||
|
||||
### Fixes:
|
||||
- [Android] - Mark Android library to be compatible only with Android.
|
||||
- [iOS] - Calendar trigger will use local or UTC time depending on which one is set on trigger.
|
||||
- Various documentation fixes.
|
||||
|
||||
## [2.3.0] - 2023-10-09
|
||||
|
||||
### Changes & Improvements:
|
||||
|
||||
- Added unified APIs for basic notifications that work on both platforms in the `Unity.Notifications` namespace.
|
||||
- Sample project modified to use unified APIs.
|
||||
- [Android] - Package now placed to Gradle project as a separate module (.androidlib).
|
||||
- [iOS] - Added APIs for checking and unregistering for push notifications.
|
||||
- [iOS] - Added new presentation options, supported since iOS 14 (list and banner).
|
||||
|
||||
### Fixes:
|
||||
- [Android] - [issue 290](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/290) Fix large icon not displayed when privided as file path.
|
||||
|
||||
## [2.2.2] - 2023-09-07
|
||||
|
||||
### Changes & Improvements:
|
||||
- [Android] - Reschedule after reboot will send all notifications that expired less than 10 minutes ago.
|
||||
- [iOS] - Remote notifications now support showInForeground key (allows to show notification while app is the foreground).
|
||||
|
||||
### Fixes:
|
||||
- [Android] - [issue 271](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/271) Fix possible ANR when sending/rescheduling after reboot.
|
||||
- [iOS] - Remote notification presentation options work regardless of when authorization is performed.
|
||||
- [iOS] - Remote notifications have presentation independent of whether callback is registered or not.
|
||||
|
||||
## [2.2.1] - 2023-07-17
|
||||
|
||||
### Fixes:
|
||||
|
||||
- Changing settings will make settings file editable if read-only by version control.
|
||||
- Documentation updates.
|
||||
|
||||
## [2.2.0] - 2023-04-06
|
||||
|
||||
### Changes & Improvements:
|
||||
- [Android] - Added support for notification channel groups.
|
||||
- [Android] - Added support for BigPicture style.
|
||||
- [Android] - Icons now support local files and URIs.
|
||||
- [Android] - Added API to check if UI with permission rationale for notifications should be shown before requesting permission.
|
||||
- [Android] - Added APIs to request exact scheduling and bypasssing battery optimizations.
|
||||
- [Android] - Minification seetings now managed automatically by package.
|
||||
- [Android] - AndroidNotificationCenter.UserPermissionToPost now will also report if notifications are blocked in Settings (Android 7+).
|
||||
- [iOS] - Added support for notification action icons (requires iOS 15).
|
||||
- [iOS] - Added property to set interruption level (requires iOS 15).
|
||||
- [iOS] - Added property to set relevance score (requires iOS 15).
|
||||
|
||||
### Fixes:
|
||||
- [Android] - Fix updating scheduled notification not updating if app is not killed.
|
||||
|
||||
## [2.1.1] - 2023-01-04
|
||||
|
||||
### Fixes:
|
||||
- [Android] - No longer use DeniedAndDontAskAgain permission response, only Denied.
|
||||
- [iOS] - [issue 205](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/205) Fix occasional crash when registering for push notifications.
|
||||
- [iOS] - [issue 242](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/242) Fix exception when OriginalUtc key is missing in data (for example notification was scheduled using older package version).
|
||||
|
||||
## [2.1.0] - 2022-09-23
|
||||
|
||||
### Changes & Improvements:
|
||||
- [Android] - Notification scheduling is made faster.
|
||||
- [Android] - Notifications can be set to not be shown when app is in foreground (contributed by InnoGames).
|
||||
- [Android] - Support for Android 13 notification permission.
|
||||
- [Android] - Support for exact scheduling on Android 12 and newer and ability to disable it.
|
||||
- [iOS] - Updated AuthorizationStatus enum with new values.
|
||||
- [iOS] - Location trigger now uses more accurate latitude/longitude and can repeat.
|
||||
- [iOS] [issue 202](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/202) Added support for different sound types and notifications without sound.
|
||||
|
||||
### Fixes:
|
||||
- [Android] - Scheduled notifications are saved in more lightweight and reliable way.
|
||||
- [Android] [issue 196](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/196) Fix occasional Resources$NotFound exception.
|
||||
- [Android] [issue 198](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/198) Fix occasional OutOfMemoryError.
|
||||
- [iOS] - When using Calendar trigger time type (Local/UTC/...) will be preserved.
|
||||
|
||||
## [2.0.2] - 2022-05-13
|
||||
|
||||
### Changes & Improvements:
|
||||
- [Android] - AndroidNotificationCenter.CheckScheduledNotificationStatus now works on devices with all API levels
|
||||
|
||||
### Fixes:
|
||||
- [Android] [issue 186](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/186) Mitigate OutOfMemory errors causing application crash
|
||||
- [Android] [issue 188](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/188) Fix unguarded API usage from level 23
|
||||
|
||||
## [2.0.1] - 2022-04-15
|
||||
|
||||
### Fixes:
|
||||
- [Android] Change exported attribute to false in the manifest for package receivers.
|
||||
- [Android] [issue 144](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/144) Fix Transaction.TooLargeException exception on some devices for notifications with large icons or more content.
|
||||
- [Android] [issue 160](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/160) Fix UnityNotificationRestartOnBootReceiver not setting FLAG_IMMUTABLE.
|
||||
|
||||
### Changes & Improvements:
|
||||
- [Android] Build will now fail if minimum SDK in Unity is lower than supported by package.
|
||||
|
||||
## [2.0.0] - 2022-02-02
|
||||
|
||||
### Fixes:
|
||||
- [1360115](https://issuetracker.unity3d.com/issues/mobile-notifications-project-settings-window-loses-focus-when-clicking-on-any-category-while-mobile-notifications-pane-is-open) Fixed focus loss when trying to switching from notifications settings.
|
||||
- [1373835]() Fixed calendar notifications for buddhist calendars on iOS.
|
||||
- [1375744](https://issuetracker.unity3d.com/issues/ios-app-freezes-slash-crashes-when-both-mobile-notifications-and-firebase-are-used) Fixed application startup hang when application uses Unity notifications or Firebase.
|
||||
- [1382960](https://issuetracker.unity3d.com/issues/ios-crash-on-pthread-kill-when-clicking-on-a-notification-that-contains-an-integer-value-in-the-apns-file) Fixed a crash when push notification JSON file has integer item at the top level.
|
||||
|
||||
### Changes & Improvements:
|
||||
- [iOS] Added support for the following notification attachements: images, sounds, videos.
|
||||
- [Android] Introduced AndroidNotificationCenter.CreateNotificationBuilder, which allows the greater customizability while creating notifications.
|
||||
- [iOS] Added support for actionable notifications
|
||||
- [iOS] Added API for registering notification categories with associated actions.
|
||||
- [Android] Optimize notification saving.
|
||||
- [iOS][Android] Added API to open notification settings
|
||||
- [Android] Added API which allows you to change notification icons in Editor.
|
||||
- [iOS] Correctly handle boolean values in UserInfo, previously boolean values were detected as numbers and get parsed as 1 or 0, now they will be correctly handled as 'true' and 'false'
|
||||
- [Android] Fixed notification package for Android 12 - NotificationRestartOnBootReceiver needs to be marked as exported in manifest file, disable exact alarms because they require special permissions.
|
||||
- [iOS] Fix NotificationSettings.iOSSettings.DefaultAuthorizationOptions having incorrect return type (was PresentationOption, now AuthorizationOption).
|
||||
- [iOS] Mobile Notifications 2.0.0: iOSNotificationTrigger.Type is no longer static and returns iOSNotificationTriggerType instead of int.
|
||||
|
||||
## [1.4.2] - 2021-07-22
|
||||
|
||||
### Fixes:
|
||||
- [Android] [issue 99](https://github.com/Unity-Technologies/com.unity.mobile.notifications/issues/99) Use FLAG_IMMUTABLE for PendingIntent
|
||||
- [iOS] Do not touch Plist file unless modifications are required
|
||||
- [iOS] [1348518](https://issuetracker.unity3d.com/product/unity/issues/guid/1348518) Fix crash in push notification (case )
|
||||
|
||||
## [1.4.1] - 2021-05-06
|
||||
|
||||
### Fixes:
|
||||
- Disable warnings and remove leftover .meta file from samples.
|
||||
|
||||
## [1.4.0] - 2021-04-29
|
||||
|
||||
### Changes & Improvements:
|
||||
- [iOS] Less memory allocations when scheduling and retrieving notifications.
|
||||
- [iOS] Exposed notification UserInfo in API.
|
||||
- [Samples] Various small improvements to the test project.
|
||||
|
||||
### Fixes:
|
||||
- [Editor] Improved layout of elements in settings.
|
||||
- [Editor] iOS header files are no longer copied when building for other platforms.
|
||||
- [iOS] Multiple authorization requests can now be performed simultaneously.
|
||||
- [iOS] [[1315365](https://issuetracker.unity3d.com/product/unity/issues/guid/1315365)] Fix TimeInterval getter.
|
||||
- [iOS] Identifier for location based notifications is no longer hardcoded and now allows multiple triggers.
|
||||
|
||||
## [1.3.2] - 2020-11-10
|
||||
|
||||
### Changes & Improvements:
|
||||
- [Editor] [[1195293](https://issuetracker.unity3d.com/product/unity/issues/guid/1195293)] Improved content scaling in the Mobile Notifications settings UI.
|
||||
- [Samples] Added IGameNotification.Data propery, so that notification data could be read in cross-platform fashion.
|
||||
- [Samples] Added IGameNotification.GetLastNotification method to retrieve the last opened notification.
|
||||
- [Samples] Added retrieval of notification enabled status on Android.
|
||||
|
||||
### Fixes:
|
||||
- [Samples] [[1258554](https://issuetracker.unity3d.com/product/unity/issues/guid/1258554)] Fixed errors and warnings on startup in the demo scene.
|
||||
- [iOS] [[1259811](https://issuetracker.unity3d.com/product/unity/issues/guid/1259811)] Fixed UNITY_USES_REMOTE_NOTIFICATIONS being falsely set to 1 when Mobile Notifications isn't used.
|
||||
- [Android] [[1271866](https://issuetracker.unity3d.com/product/unity/issues/guid/1271866)] Fixed AndroidReceivedNotificationMainThreadDispatcher allocating a new list on every frame.
|
||||
- [Editor] [[1254618](https://issuetracker.unity3d.com/product/unity/issues/guid/1254618)] Fixed "SerializedObject target has been destroyed" errors when navigating Mobile Notifications settings.
|
||||
- [Android] [[1172850](https://issuetracker.unity3d.com/product/unity/issues/guid/1172850)] Fixed Android crashing when many notifications are scheduled at once.
|
||||
|
||||
## [1.3.0] - 2020-06-02
|
||||
|
||||
### Changes & Improvements:
|
||||
- [iOS] Improved the iOS plugin code: fixed the warnings and a potential dangling pointers issue, also made the code well structured and more readable.
|
||||
- [iOS] Improved the iOS post processor code: split the function into several small ones, also fixed the wrong required iOS version.
|
||||
- [Document] Improved the document: split the single page into multiple pages to have the document better organized, also added the missing document and updated the unclear document.
|
||||
|
||||
### Fixes:
|
||||
- [Android] Supressed the deprecated warnings from Android plugin as some deprecated functions have to be called for Android 7 and below.
|
||||
- [Android] Fixed the AndroidJavaException when calling AndroidNotificationCenter.UpdateScheduledNotification().
|
||||
- [Android] Fixed the issue where AndroidNotification.Color doesn't work as expected.
|
||||
- [iOS] [[1244642](https://issuetracker.unity3d.com/product/unity/issues/guid/1244642)] Fixed the bug that users can't set the foreground presentation options to "Nothing" in the Notification Settings.
|
||||
- [Editor] [[1199310](https://issuetracker.unity3d.com/product/unity/issues/guid/1199310)] Fixed the issue that Mobile Notification icons are gray when project was built from batch mode with -nographics flag.
|
||||
|
||||
## [1.2.1-preview] - 2020-04-30
|
||||
|
||||
### Fixes:
|
||||
- [Android] [[1231218](https://issuetracker.unity3d.com/product/unity/issues/guid/1231218)] Fixed the issue by removing the "com.android.support:appcompat-v7:27.1.1" dependency.
|
||||
|
||||
## [1.2.0-preview] - 2020-04-16
|
||||
|
||||
### Changes & Improvements:
|
||||
- [Editor] Improved the Notification Settings UI, also stored the notification setting asset under 'ProjectSettings' folder rather than 'Assets' folder.
|
||||
|
||||
### Fixes:
|
||||
- Fixed a lot of notification settings UI related bugs.
|
||||
- [iOS] Fixed the issue that the authorization request callback hasn't been removed.
|
||||
- [Android] Fixed the wrong value of LockscreenVisiblity.Private.
|
||||
|
||||
## [1.1.0-preview] - 2020-03-24
|
||||
|
||||
### Changes & Improvements:
|
||||
- [Android] Exposed the java source code in the package rather than building into an .aar. Users can now debug the java code in Android Studio by exporting to a gradle project.
|
||||
- Refactored the managed plugin code to have them better structured.
|
||||
|
||||
### Fixes:
|
||||
- Fixed a potential deadlock issue in AndroidReceivedNotificationMainThreadDispatcher while handling the notification received callback.
|
||||
- [iOS] [[1221133](https://issuetracker.unity3d.com/product/unity/issues/guid/1221133)] Fixed the issue that user generated entitlements file will be rewritten by the one generated by mobile notifications.
|
||||
- [iOS] Fixed the crash on iOS if the time interval on iOSNotificationTimeIntervalTrigger is set to 0.
|
||||
- [iOS] Fixed the crash on iOS if iOSNotificationData.data is set to null.
|
||||
|
||||
## [1.0.4-preview.9] - 2020-02-10
|
||||
|
||||
### Fixes:
|
||||
|
||||
- [Android] Write all `SharedPreferences` to disk asynchronously.
|
||||
|
||||
## [1.0.4-preview.6] - 2020-01-27
|
||||
|
||||
### Fixes:
|
||||
- [Android] Duplicate notifications will no longer be scheduled after the device is restarted (when "Reschedule Notification on Device Restart" is turned on).
|
||||
- [Android] Unity's Notification package will no longer attempt to load AlarmManager intents which were not scheduledby it.
|
||||
- [Editor] It's now possible to use `Unity.Notifications' editor classes without having to create manually referenc it in your asmdef file.
|
||||
|
||||
## [1.0.4-preview.5] - 2019-11-13
|
||||
|
||||
### Fixes:
|
||||
|
||||
- [iOS/tvOS] The iOS source plugins provided by the package will no longer be included when building for Apple TV.
|
||||
|
||||
## [1.0.4-preview.4] - 2019-10-29
|
||||
|
||||
### Fixes:
|
||||
|
||||
- [Android] Icon resources will now be queried when the notification is supposed to be triggered instead of when it's scheduled. This should fix crashes due to missing resources in cases where the app is updated after a notification is scheduled but before it's delivered.
|
||||
|
||||
### Changes & Improvements:
|
||||
|
||||
- [Android] Added 'AndroidNotification.ShowTimestamp' to allow displaying timestamp on delivered notifications. The specific time stamp value can be overriden using 'AndroidNotification.CustomTimestamp' if it's not set the time at which the notification was delivered will be shown.
|
||||
|
||||
## [1.0.4-preview.3] - 2019-10-09
|
||||
|
||||
### Fixes:
|
||||
|
||||
- [iOS] Remote notification `deviceToken` is now be returned correctly (and without angle brackets)on iOS 13.
|
||||
|
||||
## [1.0.4-preview.2] - 2019-09-16
|
||||
|
||||
### Fixes:
|
||||
|
||||
- Embedded the [Notification Samples project](https://github.com/Unity-Technologies/NotificationsSamples) into the package, I can now be imported from the Package Manager UI.
|
||||
- [Android] It should now be possible to set `AndroidNotificationChannel.VibrationPattern` to null.
|
||||
|
||||
## [1.0.4-preview.1] - 2019-09-09
|
||||
|
||||
### Fixes:
|
||||
|
||||
- [Android] Trying to register an ``AndroidNotificationChannel` with a specified `VibrationPattern` should no longer trigger a missing method exception.
|
||||
- [Android][[1178665](https://issuetracker.unity3d.com/product/unity/issues/guid/1178665/)] Repeatable notifications should now work properly and should be recreated when the device is restarted. 1.0.4-preview.alpha.1
|
||||
|
||||
## [1.0.3] - 2019-08-21
|
||||
|
||||
### Fixes:
|
||||
|
||||
- [iOS] It's no longer necessary to enable the `Request Authorization on App Start` setting in UI to be able to turn on `Enable Push Notifications`.
|
||||
- [iOS] `GetLastRespondedNotification` should now properly return the notification used to open the app even if "Request Authorization on App Start" is turned off.
|
||||
- [iOS] `iOSNotification.data` field should now work properly with all valid JSON structures and not only with dictionaries and strings.
|
||||
- [Android] Added a temporary fix for an IL2CPP compilation issue on Unity 2019.2 and above.
|
||||
- Fixed an issue with NUnit Test assemblies not being detected correctly due to which exceptions were thrown in the editor.
|
||||
- [Android][1165178](https://issuetracker.unity3d.com/product/unity/issues/guid/1165178/): An Android JAVA exception should no longer be thrown when attempting to schedule more than 500 notifications on Samsung devices. Samsung seems to impose a fixed limit of concurrent Alarms so if the limit is reached all attempts to schedule new notifications will be ignored until the currently scheduled ones are triggered or manually cancelled.
|
||||
- [[1114987](https://issuetracker.unity3d.com/product/unity/issues/guid/1114987/)] Reopening the project should no longer override Mobile Notification Settings.
|
||||
- [iOS] Fixed an issue with `iOSNotification.data` not being set correctly for remote notifications if the data field is not a string. It will now return a full JSON string for the data field.
|
||||
- [Android] Notifications cancelled using `CancelScheduledNotification` or `CancelAllScheduledNotifications` should no longer be recreated on device restart if the device is restarted before the time they were supposed to be triggered.
|
||||
|
||||
### Changes & Improvements:
|
||||
|
||||
- [Android] `OnNotificationReceived`is now executed on the main Unity thread so it should now be possible to directly call Unity API methods the only work on the main thread from delegates and functions that subscribe to the this method.
|
||||
|
||||
- [iOS] Turning on `Enable Push Notifications` will add the `remote-notification ` setting to `UIBackgroundModes` array in the app’s `info.plist` file.
|
||||
|
||||
- [iOS] The notification returned by `iOSNotificationCenter.GetLastRespondedNotification()` is now cleared each time the app is moved to the background and not only when the app is fully terminated. Now it should only return the notification used to open the app or the last notification activated by the user while the app was running in the foreground.
|
||||
|
||||
- [Android] Added an option to override the Android app activity which should be opened when a notification is clicked. By default the main activity assigned to the `UnityPlayer` Java class will be used.
|
||||
|
||||
- Exposed notification settings (previously only accessible in UI) in a public Editor API (see the `Unity.Notifications.UnityNotificationSettings`) class.
|
||||
|
||||
- [Android] Increased the minimum requirements to Android 4.4 (API 19)
|
||||
|
||||
|
||||
## [1.0.2] - 2019-07-01
|
||||
|
||||
### Fixes:
|
||||
|
||||
- [iOS] Querying notification settings on iOS 10 will no longer cause a crash.
|
||||
|
||||
- [Android] Changing notification icon color now works.
|
||||
|
||||
- Minor UI improvements.
|
||||
|
||||
- Documentation improvements.
|
||||
|
||||
|
||||
## [1.0.0] - 2019-05-20
|
||||
|
||||
Includes all changes from previous preview releases.
|
||||
|
||||
### Fixes:
|
||||
|
||||
- [Android] Notification API Java classes are no longer stripped when building with Proguard enabled.
|
||||
|
||||
- [Editor] Editor settings window should not automatically detect changes to Android icon source texture assets.
|
||||
|
||||
- [Editor] The settings window should work properly and no longer throw exceptions when opening a project used with a previous version of the package.
|
||||
|
||||
- [iOS] `AuthorizationRequest` should properly finish even when the user denies the request.
|
||||
|
||||
- [Android] Canceling scheduled notifications now works correctly when the app is restarted.
|
||||
|
||||
- [iOS] Subscribing to OnNotificationReceived on iOS should now work, even if no other notification was called
|
||||
|
||||
- Notification settings should no longer dissappear if Unity is closed while settings editor screen is not opened.
|
||||
|
||||
- Compatibility fixes for Unity 2019.2.
|
||||
|
||||
- Fixed warning messages that were being thrown after importing the package on 2018.2.
|
||||
|
||||
- Compatibility fix for Unity 2019.3.
|
||||
|
||||
- Fixed issues with editor scripts and .asmdef files on Unity 2018.2.
|
||||
|
||||
- Made CancelDisplayedNotification public.
|
||||
|
||||
- Fixed an editor script issue on 2018.3.
|
||||
|
||||
|
||||
### Improvements & changes:
|
||||
|
||||
- [Android] Added support for notification groups:
|
||||
|
||||
- Set `Group` property to group multiple notifications in a single thread.
|
||||
- Enable `GroupSummary`on a notification to use it as the summary notification for it's group.
|
||||
- `GroupAlertBehaviour` can be used to override the alert behaviour for all notifications in a group.
|
||||
|
||||
- [Android] Added a `SortKey` property for Android.
|
||||
|
||||
- Used to lexicographically order this notification among other notifications from the same package or the same notification group.
|
||||
|
||||
- [Android] Changed ‘GetLastIntentData’ to ‘GetLastNotificationIntent’, it nows returns a `AndroidNotificationIntentData` object (which encapsulates the received `AndroidNotification` and it's `Channel` and `Id` fields) instead of just a string:
|
||||
|
||||
- `OnNotificationReceived` now returns `AndroidNotificationIntentData`.
|
||||
- Arbitrary data can be stored in the `AndroidNotification.intentData` field.
|
||||
|
||||
- [iOS] Added `GetLastNotification()` to `iOSNotificationCenter` :
|
||||
|
||||
- Can be used to retrieve the notification which was used to open the app.
|
||||
- If any new notifications are received while the app is active they will override the original one.
|
||||
|
||||
- [iOS] Exposed additional `iOSNotificationSettings` properties:
|
||||
|
||||
- ShowPreviewsSetting`indicates whether the app can a preview of the notification's contenton the lock screen.
|
||||
- `AlertStyle` indicates the type of alerts the user has authorized (`Banner`, `Alert` or `None`).
|
||||
|
||||
- [Android] Added an option to reshedule all non expired notification on device restart.
|
||||
|
||||
- [Android] Added a `data` field to notification object and a method to retrieve the `data` assigned to a notification that was used to open the app or bring it back from background.
|
||||
|
||||
- Changed the minimum Unity version supported by the package to 2018.3.
|
||||
|
||||
- Added an option to not add Remote Notifications capability to the Xcode project.
|
||||
|
||||
- Allow sending notifications without title or body. Allows scheduling sounds only notifications that do not have an alert or are shown in the notification center.
|
||||
|
||||
##
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4a07c2fb289d4ae98469d16e3afd781
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 607c1fecf0d392648963a0725308d841
|
||||
guid: 5c31c05b473c946669349ca2b826e4f0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to schedule notifications at exact time or approximately (saves power).
|
||||
/// Exact scheduling is available in Android 6 (API 23) and newer, lower versions always use inexact scheduling.
|
||||
/// Android 12 (API 31) or newer requires SCHEDULE_EXACT_ALARM permission and grant from user to use exact scheduling.
|
||||
/// Android 13 (API 33) or newer can use USE_EXACT_ALARM permission to use exactscheduling without requesting users grant.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum AndroidExactSchedulingOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Use exact scheduling when possible.
|
||||
/// </summary>
|
||||
ExactWhenAvailable = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Add SCHEDULE_EXACT_ALARM permission to the manifest.
|
||||
/// </summary>
|
||||
AddScheduleExactPermission = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Add USE_EXACT_ALARM permission to the manifest.
|
||||
/// </summary>
|
||||
AddUseExactAlarmPermission = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Add REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission to the manifest.
|
||||
/// Required if you want to use <see cref="AndroidNotificationCenter.RequestIgnoreBatteryOptimizations()"/>.
|
||||
/// </summary>
|
||||
AddRequestIgnoreBatteryOptimizationsPermission = 1 << 3,
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 940e1c4f6aa3144488fc37eb66fba6da
|
||||
guid: d5ef4f75b5ba00a4793f867958e727b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
#if UNITY_ANDROID
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Android;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
public class AndroidNotificationPostProcessor : IPostGenerateGradleAndroidProject
|
||||
{
|
||||
const string kAndroidNamespaceURI = "http://schemas.android.com/apk/res/android";
|
||||
|
||||
public int callbackOrder { get { return 0; } }
|
||||
|
||||
public void OnPostGenerateGradleAndroidProject(string projectPath)
|
||||
{
|
||||
projectPath = Path.Combine(projectPath, "mobilenotifications.androidlib");
|
||||
if (!Directory.Exists(projectPath))
|
||||
throw new Exception("mobilenotifications module not found in gradle project");
|
||||
|
||||
CopyNotificationResources(projectPath);
|
||||
|
||||
InjectAndroidManifest(projectPath);
|
||||
}
|
||||
|
||||
private void CopyNotificationResources(string projectPath)
|
||||
{
|
||||
// Get the icons set in the UnityNotificationEditorManager and write them to the res folder, then we can use the icons as res.
|
||||
var icons = NotificationSettingsManager.Initialize().GenerateDrawableResourcesForExport();
|
||||
foreach (var icon in icons)
|
||||
{
|
||||
var fileInfo = new FileInfo(string.Format("{0}/src/main/res/{1}", projectPath, icon.Key));
|
||||
if (fileInfo.Directory != null)
|
||||
{
|
||||
fileInfo.Directory.Create();
|
||||
File.WriteAllBytes(fileInfo.FullName, icon.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ManifestSettings
|
||||
{
|
||||
public bool UseCustomActivity;
|
||||
public string CustomActivity;
|
||||
public bool RescheduleOnRestart;
|
||||
public AndroidExactSchedulingOption ExactAlarm;
|
||||
}
|
||||
|
||||
private void InjectAndroidManifest(string projectPath)
|
||||
{
|
||||
var manifestPath = string.Format("{0}/src/main/AndroidManifest.xml", projectPath);
|
||||
if (!File.Exists(manifestPath))
|
||||
throw new FileNotFoundException(string.Format("'{0}' doesn't exist.", manifestPath));
|
||||
|
||||
XmlDocument manifestDoc = new XmlDocument();
|
||||
manifestDoc.Load(manifestPath);
|
||||
|
||||
var settings = NotificationSettingsManager.Initialize().AndroidNotificationSettingsFlat;
|
||||
var manifestSettings = new ManifestSettings()
|
||||
{
|
||||
UseCustomActivity = GetSetting<bool>(settings, NotificationSettings.AndroidSettings.USE_CUSTOM_ACTIVITY),
|
||||
CustomActivity = GetSetting<string>(settings, NotificationSettings.AndroidSettings.CUSTOM_ACTIVITY_CLASS),
|
||||
RescheduleOnRestart = GetSetting<bool>(settings, NotificationSettings.AndroidSettings.RESCHEDULE_ON_RESTART),
|
||||
ExactAlarm = GetSetting<AndroidExactSchedulingOption>(settings, NotificationSettings.AndroidSettings.EXACT_ALARM),
|
||||
};
|
||||
|
||||
InjectAndroidManifest(manifestPath, manifestDoc, manifestSettings);
|
||||
|
||||
manifestDoc.Save(manifestPath);
|
||||
}
|
||||
|
||||
internal static void InjectAndroidManifest(string manifestPath, XmlDocument manifestDoc, ManifestSettings settings)
|
||||
{
|
||||
if (settings.UseCustomActivity)
|
||||
AppendAndroidMetadataField(manifestPath, manifestDoc, "custom_notification_android_activity", settings.CustomActivity);
|
||||
|
||||
if (settings.RescheduleOnRestart)
|
||||
{
|
||||
AppendAndroidMetadataField(manifestPath, manifestDoc, "reschedule_notifications_on_restart", "true");
|
||||
AppendAndroidPermissionField(manifestPath, manifestDoc, "android.permission.RECEIVE_BOOT_COMPLETED");
|
||||
}
|
||||
|
||||
bool enableExact = (settings.ExactAlarm & AndroidExactSchedulingOption.ExactWhenAvailable) != 0;
|
||||
AppendAndroidMetadataField(manifestPath, manifestDoc, "com.unity.androidnotifications.exact_scheduling", enableExact ? "1" : "0");
|
||||
if (enableExact)
|
||||
{
|
||||
bool scheduleExact = (settings.ExactAlarm & AndroidExactSchedulingOption.AddScheduleExactPermission) != 0;
|
||||
bool useExact = (settings.ExactAlarm & AndroidExactSchedulingOption.AddUseExactAlarmPermission) != 0;
|
||||
// as documented here: https://developer.android.com/reference/android/Manifest.permission#USE_EXACT_ALARM
|
||||
// only one of these two attributes should be used or max sdk set so on any device it's one or the other
|
||||
if (scheduleExact)
|
||||
AppendAndroidPermissionField(manifestPath, manifestDoc, "android.permission.SCHEDULE_EXACT_ALARM", useExact ? "32" : null);
|
||||
if (useExact)
|
||||
AppendAndroidPermissionField(manifestPath, manifestDoc, "android.permission.USE_EXACT_ALARM");
|
||||
|
||||
// Battery optimizations must use "uses-permission-sdk-23", regular uses-permission does not work
|
||||
if ((settings.ExactAlarm & AndroidExactSchedulingOption.AddRequestIgnoreBatteryOptimizationsPermission) != 0)
|
||||
AppendAndroidPermissionField(manifestPath, manifestDoc, "uses-permission-sdk-23", "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", null);
|
||||
}
|
||||
}
|
||||
|
||||
private static T GetSetting<T>(List<NotificationSetting> settings, string key)
|
||||
{
|
||||
return (T)settings.Find(i => i.Key == key).Value;
|
||||
}
|
||||
|
||||
internal static void AppendAndroidPermissionField(string manifestPath, XmlDocument xmlDoc, string name, string maxSdk = null)
|
||||
{
|
||||
AppendAndroidPermissionField(manifestPath, xmlDoc, "uses-permission", name, maxSdk);
|
||||
}
|
||||
|
||||
internal static void AppendAndroidPermissionField(string manifestPath, XmlDocument xmlDoc, string tagName, string name, string maxSdk)
|
||||
{
|
||||
var manifestNode = xmlDoc.SelectSingleNode("manifest");
|
||||
if (manifestNode == null)
|
||||
throw new ArgumentException(string.Format("Missing 'manifest' node in '{0}'.", manifestPath));
|
||||
|
||||
XmlElement metaDataNode = null;
|
||||
foreach (XmlNode node in manifestNode.ChildNodes)
|
||||
{
|
||||
if (!(node is XmlElement) || node.Name != tagName)
|
||||
continue;
|
||||
|
||||
var element = (XmlElement)node;
|
||||
var elementName = element.GetAttribute("name", kAndroidNamespaceURI);
|
||||
if (elementName == name)
|
||||
{
|
||||
if (maxSdk == null)
|
||||
return;
|
||||
var maxSdkAttr = element.GetAttribute("maxSdkVersion", kAndroidNamespaceURI);
|
||||
if (!string.IsNullOrEmpty(maxSdkAttr))
|
||||
return;
|
||||
metaDataNode = element;
|
||||
}
|
||||
}
|
||||
|
||||
if (metaDataNode == null)
|
||||
{
|
||||
metaDataNode = xmlDoc.CreateElement(tagName);
|
||||
metaDataNode.SetAttribute("name", kAndroidNamespaceURI, name);
|
||||
}
|
||||
if (maxSdk != null)
|
||||
metaDataNode.SetAttribute("maxSdkVersion", kAndroidNamespaceURI, maxSdk);
|
||||
|
||||
manifestNode.AppendChild(metaDataNode);
|
||||
}
|
||||
|
||||
internal static void AppendAndroidMetadataField(string manifestPath, XmlDocument xmlDoc, string name, string value)
|
||||
{
|
||||
var applicationNode = xmlDoc.SelectSingleNode("manifest/application");
|
||||
if (applicationNode == null)
|
||||
throw new ArgumentException(string.Format("Missing 'application' node in '{0}'.", manifestPath));
|
||||
|
||||
var nodes = xmlDoc.SelectNodes("manifest/application/meta-data");
|
||||
if (nodes != null)
|
||||
{
|
||||
// Check if there is a 'meta-data' with the same name.
|
||||
foreach (XmlNode node in nodes)
|
||||
{
|
||||
var element = node as XmlElement;
|
||||
if (element == null)
|
||||
continue;
|
||||
|
||||
var elementName = element.GetAttribute("name", kAndroidNamespaceURI);
|
||||
if (elementName == name)
|
||||
{
|
||||
element.SetAttribute("value", kAndroidNamespaceURI, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XmlElement metaDataNode = xmlDoc.CreateElement("meta-data");
|
||||
metaDataNode.SetAttribute("name", kAndroidNamespaceURI, name);
|
||||
metaDataNode.SetAttribute("value", kAndroidNamespaceURI, value);
|
||||
|
||||
applicationNode.AppendChild(metaDataNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ab62d805bcb24c96b7ef39a230d410e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Notifications.Tests")]
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3703be1dcf9d604a85439fd2feb1e0d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
[System.Serializable]
|
||||
internal class DrawableResourceData
|
||||
{
|
||||
public string Id;
|
||||
public NotificationIconType Type;
|
||||
public Texture2D Asset;
|
||||
|
||||
private bool m_IsValid = false;
|
||||
private List<string> m_Errors = null;
|
||||
private Texture2D m_PreviewTexture;
|
||||
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_IsValid == false && m_Errors == null)
|
||||
Verify();
|
||||
|
||||
return m_IsValid;
|
||||
}
|
||||
}
|
||||
|
||||
public string[] Errors
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_IsValid == false && m_Errors == null)
|
||||
Verify();
|
||||
|
||||
return m_Errors.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public Texture2D GetPreviewTexture(bool update)
|
||||
{
|
||||
if (Asset == null)
|
||||
return null;
|
||||
|
||||
if (m_IsValid && (m_PreviewTexture == null || update))
|
||||
m_PreviewTexture = TextureAssetUtils.ProcessTextureForType(Asset, Type);
|
||||
|
||||
return m_PreviewTexture;
|
||||
}
|
||||
|
||||
internal bool Initialized()
|
||||
{
|
||||
return !string.IsNullOrEmpty(Id) && Asset != null;
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
m_IsValid = false;
|
||||
m_Errors = null;
|
||||
m_PreviewTexture = null;
|
||||
}
|
||||
|
||||
public bool Verify()
|
||||
{
|
||||
m_IsValid = TextureAssetUtils.VerifyTextureByType(Asset, Type, out m_Errors);
|
||||
return m_IsValid;
|
||||
}
|
||||
|
||||
public string GenerateErrorString()
|
||||
{
|
||||
var errors = Errors;
|
||||
|
||||
var errorString = string.Empty;
|
||||
for (var i = 0; i < errors.Length; i++)
|
||||
{
|
||||
errorString += string.Format("{0}{1}", errors[i], i + 1 >= errors.Length ? "." : ", ");
|
||||
}
|
||||
|
||||
return errorString;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7bb3ae4e071b4f599d584ac995b185f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
#if UNITY_IOS
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEngine;
|
||||
|
||||
public class NotificationAutoConfig : IPreprocessBuildWithReport
|
||||
{
|
||||
public int callbackOrder => 0;
|
||||
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
if (report.summary.platform != BuildTarget.iOS) return;
|
||||
|
||||
// 1. 构造文件绝对路径
|
||||
string settingsPath = "ProjectSettings/NotificationsSettings.asset";
|
||||
string fullPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", settingsPath));
|
||||
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
Debug.LogError($"[NotificationAutoConfig] 文件不存在: {fullPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 通过反射获取 NotificationSettingsManager 类型
|
||||
Type managerType = null;
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
managerType = asm.GetType("Unity.Notifications.NotificationSettingsManager");
|
||||
if (managerType != null) break;
|
||||
}
|
||||
|
||||
if (managerType == null)
|
||||
{
|
||||
Debug.LogError("[NotificationAutoConfig] 无法找到 NotificationSettingsManager 类型。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 读取文件 JSON,创建 Manager 实例并用 JSON 覆盖数据
|
||||
string json = File.ReadAllText(fullPath);
|
||||
ScriptableObject managerInstance = ScriptableObject.CreateInstance(managerType);
|
||||
EditorJsonUtility.FromJsonOverwrite(json, managerInstance);
|
||||
|
||||
// 4. 获取 m_iOSNotificationSettingsValues 字段(NotificationSettingsCollection)
|
||||
FieldInfo settingsField = managerType.GetField("m_iOSNotificationSettingsValues",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (settingsField == null)
|
||||
{
|
||||
Debug.LogError("[NotificationAutoConfig] 找不到 m_iOSNotificationSettingsValues 字段。");
|
||||
ScriptableObject.DestroyImmediate(managerInstance);
|
||||
return;
|
||||
}
|
||||
|
||||
object iOSSettings = settingsField.GetValue(managerInstance);
|
||||
if (iOSSettings == null)
|
||||
{
|
||||
Debug.LogError("[NotificationAutoConfig] m_iOSNotificationSettingsValues 为 null。");
|
||||
ScriptableObject.DestroyImmediate(managerInstance);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 获取索引器(this[string key])
|
||||
Type collectionType = iOSSettings.GetType();
|
||||
PropertyInfo indexer = collectionType.GetProperty("Item", BindingFlags.Public | BindingFlags.Instance);
|
||||
if (indexer == null)
|
||||
{
|
||||
Debug.LogError("[NotificationAutoConfig] 无法找到 NotificationSettingsCollection 的索引器。");
|
||||
ScriptableObject.DestroyImmediate(managerInstance);
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. 修改目标键的值
|
||||
bool changed = false;
|
||||
changed |= SetSettingValue(indexer, iOSSettings, "UnityNotificationRequestAuthorizationOnAppLaunch", false);
|
||||
changed |= SetSettingValue(indexer, iOSSettings, "UnityAddRemoteNotificationCapability", true);
|
||||
changed |= SetSettingValue(indexer, iOSSettings, "UnityUseAPSReleaseEnvironment", true);
|
||||
|
||||
// 7. 写回文件并刷新
|
||||
if (changed)
|
||||
{
|
||||
string newJson = EditorJsonUtility.ToJson(managerInstance, true);
|
||||
File.WriteAllText(fullPath, newJson);
|
||||
AssetDatabase.Refresh();
|
||||
Debug.Log("[NotificationAutoConfig] iOS 通知配置已成功更新。");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[NotificationAutoConfig] 配置已是预期状态,无需修改。");
|
||||
}
|
||||
|
||||
ScriptableObject.DestroyImmediate(managerInstance);
|
||||
}
|
||||
|
||||
private bool SetSettingValue(PropertyInfo indexer, object collection, string key, bool value)
|
||||
{
|
||||
object current = indexer.GetValue(collection, new object[] { key });
|
||||
if (current is bool b && b == value)
|
||||
return false;
|
||||
|
||||
indexer.SetValue(collection, value, new object[] { key });
|
||||
Debug.Log($"[NotificationAutoConfig] 修改 {key} = {value}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4777ae7b66247243bd33ff202f7f4b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Notification icon type.
|
||||
/// </summary>
|
||||
public enum NotificationIconType
|
||||
{
|
||||
/// <summary>
|
||||
/// Denotes small icon.
|
||||
/// </summary>
|
||||
Small = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Denotes large icon
|
||||
/// </summary>
|
||||
Large = 1,
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4598419a0a7a742fea832062d225b4b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Assets/TCJPLYRM1xJTSDK/ThirdParty/com.unity.mobile.notifications@2.3.2/Editor/NotificationSetting.cs
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
internal class NotificationSetting
|
||||
{
|
||||
public string Key;
|
||||
public string Label;
|
||||
public string Tooltip;
|
||||
public object Value;
|
||||
public bool WriteToPlist;
|
||||
|
||||
public List<NotificationSetting> Dependencies;
|
||||
|
||||
public NotificationSetting(string key, string label, string tooltip, object value, bool writeToPlist = true,
|
||||
List<NotificationSetting> dependencies = null)
|
||||
{
|
||||
this.Key = key;
|
||||
this.Label = label;
|
||||
this.Tooltip = tooltip;
|
||||
this.Value = value;
|
||||
this.WriteToPlist = writeToPlist;
|
||||
this.Dependencies = dependencies;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb2bc06c3d6c8474cb3af9bf67470a71
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
using Unity.Notifications.iOS;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Class used to access notification settings for a specific platform.
|
||||
/// </summary>
|
||||
public class NotificationSettings
|
||||
{
|
||||
private static NotificationSetting GetSetting(BuildTargetGroup target, string key)
|
||||
{
|
||||
var manager = NotificationSettingsManager.Initialize();
|
||||
|
||||
NotificationSetting setting = null;
|
||||
if (target == BuildTargetGroup.Android)
|
||||
{
|
||||
setting = manager.AndroidNotificationSettingsFlat.Find(i => i.Key == key);
|
||||
}
|
||||
else if (target == BuildTargetGroup.iOS)
|
||||
{
|
||||
setting = manager.iOSNotificationSettingsFlat.Find(i => i.Key == key);
|
||||
}
|
||||
|
||||
return setting;
|
||||
}
|
||||
|
||||
private static void SetSettingValue<T>(BuildTargetGroup target, string key, T value)
|
||||
{
|
||||
var manager = NotificationSettingsManager.Initialize();
|
||||
|
||||
NotificationSetting setting = GetSetting(target, key);
|
||||
if (setting != null)
|
||||
{
|
||||
setting.Value = value;
|
||||
manager.SaveSetting(setting, target);
|
||||
}
|
||||
}
|
||||
|
||||
private static T GetSettingValue<T>(BuildTargetGroup target, string key)
|
||||
{
|
||||
var setting = GetSetting(target, key);
|
||||
return (T)setting.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class used to access Android-specific notification settings.
|
||||
/// </summary>
|
||||
public static class AndroidSettings
|
||||
{
|
||||
internal static readonly string RESCHEDULE_ON_RESTART = "UnityNotificationAndroidRescheduleOnDeviceRestart";
|
||||
internal static readonly string EXACT_ALARM = "UnityNotificationAndroidScheduleExactAlarms";
|
||||
internal static readonly string USE_CUSTOM_ACTIVITY = "UnityNotificationAndroidUseCustomActivity";
|
||||
internal static readonly string CUSTOM_ACTIVITY_CLASS = "UnityNotificationAndroidCustomActivityString";
|
||||
|
||||
/// <summary>
|
||||
/// By default AndroidSettings removes all scheduled notifications when the device is restarted. Enable this to automatically reschedule all non expired notifications when the device is turned back on.
|
||||
/// </summary>
|
||||
public static bool RescheduleOnDeviceRestart
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<bool>(BuildTargetGroup.Android, RESCHEDULE_ON_RESTART);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<bool>(BuildTargetGroup.Android, RESCHEDULE_ON_RESTART, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable this if you want to override the activity which will opened when the user click on the notification. By default activity assigned to `com.unity3d.player.UnityPlayer.currentActivity` will be used.
|
||||
/// </summary>
|
||||
public static bool UseCustomActivity
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<bool>(BuildTargetGroup.Android, USE_CUSTOM_ACTIVITY);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<bool>(BuildTargetGroup.Android, USE_CUSTOM_ACTIVITY, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The full class name of the activity that you wish to be assigned to the notification.
|
||||
/// </summary>
|
||||
public static string CustomActivityString
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<string>(BuildTargetGroup.Android, CUSTOM_ACTIVITY_CLASS);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<string>(BuildTargetGroup.Android, CUSTOM_ACTIVITY_CLASS, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A set of flags indicating whether to use exact scheduling and add supporting permissions.
|
||||
/// </summary>
|
||||
public static AndroidExactSchedulingOption ExactSchedulingOption
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<AndroidExactSchedulingOption>(BuildTargetGroup.Android, EXACT_ALARM);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<AndroidExactSchedulingOption>(BuildTargetGroup.Android, EXACT_ALARM, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add image to notification settings.
|
||||
/// </summary>
|
||||
/// <param name="id">Image identifier</param>
|
||||
/// <param name="image">Image texture, must be obtained from asset database</param>
|
||||
/// <param name="type">Image type</param>
|
||||
public static void AddDrawableResource(string id, Texture2D image, NotificationIconType type)
|
||||
{
|
||||
var manager = NotificationSettingsManager.Initialize();
|
||||
manager.AddDrawableResource(id, image, type);
|
||||
SettingsService.RepaintAllSettingsWindow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove icon at given index from notification settings.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of image to remove</param>
|
||||
public static void RemoveDrawableResource(int index)
|
||||
{
|
||||
var manager = NotificationSettingsManager.Initialize();
|
||||
manager.RemoveDrawableResourceByIndex(index);
|
||||
SettingsService.RepaintAllSettingsWindow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove icon with given identifier from notification settings.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the image to remove</param>
|
||||
public static void RemoveDrawableResource(string id)
|
||||
{
|
||||
var manager = NotificationSettingsManager.Initialize();
|
||||
manager.RemoveDrawableResourceById(id);
|
||||
SettingsService.RepaintAllSettingsWindow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all images from notification settings.
|
||||
/// </summary>
|
||||
public static void ClearDrawableResources()
|
||||
{
|
||||
var manager = NotificationSettingsManager.Initialize();
|
||||
manager.ClearDrawableResources();
|
||||
SettingsService.RepaintAllSettingsWindow();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class used to access iOS-specific notification settings.
|
||||
/// </summary>
|
||||
public static class iOSSettings
|
||||
{
|
||||
internal static readonly string REQUEST_AUTH_ON_LAUNCH = "UnityNotificationRequestAuthorizationOnAppLaunch";
|
||||
internal static readonly string DEFAULT_AUTH_OPTS = "UnityNotificationDefaultAuthorizationOptions";
|
||||
internal static readonly string ADD_PUSH_CAPABILITY = "UnityAddRemoteNotificationCapability";
|
||||
internal static readonly string REQUEST_PUSH_AUTH_ON_LAUNCH = "UnityNotificationRequestAuthorizationForRemoteNotificationsOnAppLaunch";
|
||||
internal static readonly string PUSH_NOTIFICATION_PRESENTATION = "UnityRemoteNotificationForegroundPresentationOptions";
|
||||
internal static readonly string USE_APS_RELEASE = "UnityUseAPSReleaseEnvironment";
|
||||
internal static readonly string USE_LOCATION_TRIGGER = "UnityUseLocationNotificationTrigger";
|
||||
internal static readonly string ADD_TIME_SENSITIVE_ENTITLEMENT = "UnityAddTimeSensitiveEntitlement";
|
||||
|
||||
/// <summary>
|
||||
/// It's recommended to make the authorization request during the app's launch cycle. If this is enabled the user will be shown the authorization pop-up immediately when the app launches. If it’s unchecked you’ll need to manually create an AuthorizationRequest before your app can send or receive notifications.
|
||||
/// </summary>
|
||||
public static bool RequestAuthorizationOnAppLaunch
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<bool>(BuildTargetGroup.iOS, REQUEST_AUTH_ON_LAUNCH);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<bool>(BuildTargetGroup.iOS, REQUEST_AUTH_ON_LAUNCH, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the notification interaction types your app will include in the authorisation request if RequestAuthorizationOnAppLaunch is enabled. Alternatively you can specify them when creating a `AuthorizationRequest` from a script.
|
||||
/// </summary>
|
||||
public static AuthorizationOption DefaultAuthorizationOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<AuthorizationOption>(BuildTargetGroup.iOS, DEFAULT_AUTH_OPTS);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<AuthorizationOption>(BuildTargetGroup.iOS, DEFAULT_AUTH_OPTS, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable this to add the push notification capability to you Xcode project.
|
||||
/// </summary>
|
||||
public static bool AddRemoteNotificationCapability
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<bool>(BuildTargetGroup.iOS, ADD_PUSH_CAPABILITY);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<bool>(BuildTargetGroup.iOS, ADD_PUSH_CAPABILITY, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this is enabled the app will automatically register your app with APNs after the launch which would enable it to receive remote notifications. You’ll have to manually create a AuthorizationRequest to get the device token.
|
||||
/// </summary>
|
||||
public static bool NotificationRequestAuthorizationForRemoteNotificationsOnAppLaunch
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<bool>(BuildTargetGroup.iOS, REQUEST_PUSH_AUTH_ON_LAUNCH);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<bool>(BuildTargetGroup.iOS, REQUEST_PUSH_AUTH_ON_LAUNCH, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default presentation options for received remote notifications. In order for the specified presentation options to be used your app must had received the authorization to use them (the user might change it at any time).
|
||||
/// </summary>
|
||||
public static PresentationOption RemoteNotificationForegroundPresentationOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<PresentationOption>(BuildTargetGroup.iOS, PUSH_NOTIFICATION_PRESENTATION);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<PresentationOption>(BuildTargetGroup.iOS, PUSH_NOTIFICATION_PRESENTATION, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable this when signing the app with a production certificate.
|
||||
/// </summary>
|
||||
public static bool UseAPSReleaseEnvironment
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<bool>(BuildTargetGroup.iOS, USE_APS_RELEASE);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<bool>(BuildTargetGroup.iOS, USE_APS_RELEASE, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If you intend to use the iOSNotificationLocationTrigger in your notifications you must include the CoreLocation framework in your project.
|
||||
/// </summary>
|
||||
public static bool UseLocationNotificationTrigger
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<bool>(BuildTargetGroup.iOS, USE_LOCATION_TRIGGER);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<bool>(BuildTargetGroup.iOS, USE_LOCATION_TRIGGER, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add entitlement to enable notifications with time-sensitive interruption level.
|
||||
/// </summary>
|
||||
public static bool AddTimeSensitiveEntitlement
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetSettingValue<bool>(BuildTargetGroup.iOS, ADD_TIME_SENSITIVE_ENTITLEMENT);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetSettingValue<bool>(BuildTargetGroup.iOS, ADD_TIME_SENSITIVE_ENTITLEMENT, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af42dd3f8d8d54934ab3f4bdc740f0f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
[Serializable]
|
||||
internal class NotificationSettingsCollection
|
||||
{
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("keys")]
|
||||
List<string> m_Keys;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("values")]
|
||||
List<string> m_Values;
|
||||
|
||||
public NotificationSettingsCollection()
|
||||
{
|
||||
m_Keys = new List<string>();
|
||||
m_Values = new List<string>();
|
||||
}
|
||||
|
||||
public bool Contains(string key)
|
||||
{
|
||||
return m_Keys.Contains(key);
|
||||
}
|
||||
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
var index = m_Keys.IndexOf(key);
|
||||
if (index == -1 || m_Values.Count <= index)
|
||||
return null;
|
||||
|
||||
int intValue;
|
||||
if (int.TryParse(m_Values[index], out intValue))
|
||||
{
|
||||
return intValue;
|
||||
}
|
||||
|
||||
bool boolValue;
|
||||
if (bool.TryParse(m_Values[index], out boolValue))
|
||||
{
|
||||
return boolValue;
|
||||
}
|
||||
|
||||
return m_Values[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
string strValue;
|
||||
|
||||
if (value is Enum)
|
||||
{
|
||||
strValue = ((int)value).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
strValue = value.ToString();
|
||||
}
|
||||
|
||||
var index = m_Keys.IndexOf(key);
|
||||
if (index == -1)
|
||||
{
|
||||
m_Keys.Add(key);
|
||||
m_Values.Add(strValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Values[index] = strValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 962a150b07e814ff0a531dd29ee67940
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
using Unity.Notifications.iOS;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
[HelpURL("Packages/com.unity.mobile.notifications/documentation.html")]
|
||||
internal class NotificationSettingsManager : ScriptableObject
|
||||
{
|
||||
internal static readonly string k_SettingsPath = "ProjectSettings/NotificationsSettings.asset";
|
||||
|
||||
private static NotificationSettingsManager s_SettingsManager;
|
||||
|
||||
[FormerlySerializedAs("toolbarInt")]
|
||||
public int ToolbarIndex = 0;
|
||||
|
||||
public List<NotificationSetting> iOSNotificationSettings;
|
||||
public List<NotificationSetting> AndroidNotificationSettings;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("iOSNotificationEditorSettingsValues")]
|
||||
private NotificationSettingsCollection m_iOSNotificationSettingsValues;
|
||||
|
||||
[SerializeField]
|
||||
[FormerlySerializedAs("AndroidNotificationEditorSettingsValues")]
|
||||
private NotificationSettingsCollection m_AndroidNotificationSettingsValues;
|
||||
|
||||
[FormerlySerializedAs("TrackedResourceAssets")]
|
||||
public List<DrawableResourceData> DrawableResources = new List<DrawableResourceData>();
|
||||
|
||||
public List<NotificationSetting> iOSNotificationSettingsFlat
|
||||
{
|
||||
get
|
||||
{
|
||||
var target = new List<NotificationSetting>();
|
||||
FlattenList(iOSNotificationSettings, target);
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
public List<NotificationSetting> AndroidNotificationSettingsFlat
|
||||
{
|
||||
get
|
||||
{
|
||||
var target = new List<NotificationSetting>();
|
||||
FlattenList(AndroidNotificationSettings, target);
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
private void FlattenList(List<NotificationSetting> source, List<NotificationSetting> target)
|
||||
{
|
||||
foreach (var setting in source)
|
||||
{
|
||||
target.Add(setting);
|
||||
|
||||
if (setting.Dependencies != null)
|
||||
{
|
||||
FlattenList(setting.Dependencies, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static NotificationSettingsManager Initialize()
|
||||
{
|
||||
if (s_SettingsManager != null)
|
||||
return s_SettingsManager;
|
||||
|
||||
var settingsManager = CreateInstance<NotificationSettingsManager>();
|
||||
bool dirty = false;
|
||||
|
||||
if (File.Exists(k_SettingsPath))
|
||||
{
|
||||
var settingsJson = File.ReadAllText(k_SettingsPath);
|
||||
if (!string.IsNullOrEmpty(settingsJson))
|
||||
EditorJsonUtility.FromJsonOverwrite(settingsJson, settingsManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only migrate if there is no new settings asset under k_SettingsPath.
|
||||
dirty = MigrateFromLegacySettings(settingsManager);
|
||||
}
|
||||
|
||||
if (settingsManager.m_iOSNotificationSettingsValues == null)
|
||||
{
|
||||
settingsManager.m_iOSNotificationSettingsValues = new NotificationSettingsCollection();
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (settingsManager.m_AndroidNotificationSettingsValues == null)
|
||||
{
|
||||
settingsManager.m_AndroidNotificationSettingsValues = new NotificationSettingsCollection();
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
// Create the settings for iOS.
|
||||
settingsManager.iOSNotificationSettings = new List<NotificationSetting>()
|
||||
{
|
||||
new NotificationSetting(
|
||||
NotificationSettings.iOSSettings.REQUEST_AUTH_ON_LAUNCH,
|
||||
"Request Authorization on App Launch",
|
||||
"It's recommended to make the authorization request during the app's launch cycle. If this is enabled the authorization pop-up will show up immediately during launching. Otherwise you need to manually create an AuthorizationRequest before sending or receiving notifications.",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.iOSSettings.REQUEST_AUTH_ON_LAUNCH, true, false),
|
||||
dependencies: new List<NotificationSetting>()
|
||||
{
|
||||
new NotificationSetting(
|
||||
NotificationSettings.iOSSettings.DEFAULT_AUTH_OPTS,
|
||||
"Default Notification Authorization Options",
|
||||
"Configure the notification interaction types which will be included in the authorization request if \"Request Authorization on App Launch\" is enabled.",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.iOSSettings.DEFAULT_AUTH_OPTS,
|
||||
AuthorizationOption.Alert | AuthorizationOption.Badge | AuthorizationOption.Sound, false)),
|
||||
}),
|
||||
new NotificationSetting(
|
||||
NotificationSettings.iOSSettings.ADD_PUSH_CAPABILITY,
|
||||
"Enable Push Notifications",
|
||||
"Enable this to add the push notification capability to the Xcode project, also to retrieve the device token from an AuthorizationRequest.",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.iOSSettings.ADD_PUSH_CAPABILITY, false, false),
|
||||
false,
|
||||
new List<NotificationSetting>()
|
||||
{
|
||||
new NotificationSetting(
|
||||
NotificationSettings.iOSSettings.REQUEST_PUSH_AUTH_ON_LAUNCH,
|
||||
"Register for Push Notifications on App Launch",
|
||||
"Enable this to automatically register your app with APNs after launching to receive remote notifications. You need to manually create an AuthorizationRequest to get the device token.",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.iOSSettings.REQUEST_PUSH_AUTH_ON_LAUNCH, false, false)),
|
||||
new NotificationSetting(
|
||||
NotificationSettings.iOSSettings.PUSH_NOTIFICATION_PRESENTATION,
|
||||
"Remote Notification Foreground Presentation Options",
|
||||
"Configure the default presentation options for received remote notifications. In order to use the specified presentation options, your app must have received the authorization (the user might change it at any time).",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.iOSSettings.PUSH_NOTIFICATION_PRESENTATION, (PresentationOption)iOSPresentationOption.All, false)),
|
||||
new NotificationSetting(NotificationSettings.iOSSettings.USE_APS_RELEASE,
|
||||
"Enable Release Environment for APS",
|
||||
"Enable this when signing the app with a production certificate.",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.iOSSettings.USE_APS_RELEASE, false, false),
|
||||
false),
|
||||
}),
|
||||
new NotificationSetting(NotificationSettings.iOSSettings.USE_LOCATION_TRIGGER,
|
||||
"Include CoreLocation Framework",
|
||||
"Include the CoreLocation framework to use the iOSNotificationLocationTrigger in your project.",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.iOSSettings.USE_LOCATION_TRIGGER, false, false),
|
||||
false),
|
||||
new NotificationSetting(NotificationSettings.iOSSettings.ADD_TIME_SENSITIVE_ENTITLEMENT,
|
||||
"Enable time sensitive notifications",
|
||||
"Enable to add entitlement for notifications with time sensitive interruption level",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.iOSSettings.ADD_TIME_SENSITIVE_ENTITLEMENT, false, false),
|
||||
false),
|
||||
};
|
||||
|
||||
// Create the settings for Android.
|
||||
settingsManager.AndroidNotificationSettings = new List<NotificationSetting>()
|
||||
{
|
||||
new NotificationSetting(
|
||||
NotificationSettings.AndroidSettings.RESCHEDULE_ON_RESTART,
|
||||
"Reschedule on Device Restart",
|
||||
"Enable this to automatically reschedule all non-expired notifications after device restart. By default AndroidSettings removes all scheduled notifications after restarting.",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.AndroidSettings.RESCHEDULE_ON_RESTART, false, true)),
|
||||
new NotificationSetting(
|
||||
NotificationSettings.AndroidSettings.EXACT_ALARM,
|
||||
"Schedule at exact time",
|
||||
"Whether notifications should appear at exact time or approximate",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.AndroidSettings.EXACT_ALARM, (AndroidExactSchedulingOption)0, true)),
|
||||
new NotificationSetting(
|
||||
NotificationSettings.AndroidSettings.USE_CUSTOM_ACTIVITY,
|
||||
"Use Custom Activity",
|
||||
"Enable this to override the activity which will be opened when the user taps the notification.",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.AndroidSettings.USE_CUSTOM_ACTIVITY, false, true),
|
||||
dependencies: new List<NotificationSetting>()
|
||||
{
|
||||
new NotificationSetting(
|
||||
NotificationSettings.AndroidSettings.CUSTOM_ACTIVITY_CLASS,
|
||||
"Custom Activity Name",
|
||||
"The full class name of the activity which will be assigned to the notification.",
|
||||
settingsManager.GetOrAddNotificationSettingValue(NotificationSettings.AndroidSettings.CUSTOM_ACTIVITY_CLASS, "com.unity3d.player.UnityPlayerActivity", true))
|
||||
})
|
||||
};
|
||||
|
||||
settingsManager.SaveSettings(dirty);
|
||||
|
||||
s_SettingsManager = settingsManager;
|
||||
return s_SettingsManager;
|
||||
}
|
||||
|
||||
private static bool MigrateFromLegacySettings(NotificationSettingsManager settingsManager)
|
||||
{
|
||||
const string k_LegacyAssetPath = "Assets/Editor/com.unity.mobile.notifications/NotificationSettings.asset";
|
||||
if (!File.Exists(k_LegacyAssetPath))
|
||||
return false;
|
||||
|
||||
var settingsManagerLegacy = AssetDatabase.LoadAssetAtPath<NotificationSettingsManager>(k_LegacyAssetPath);
|
||||
if (settingsManagerLegacy == null)
|
||||
return false;
|
||||
|
||||
settingsManager.ToolbarIndex = settingsManagerLegacy.ToolbarIndex;
|
||||
settingsManager.m_iOSNotificationSettingsValues = settingsManagerLegacy.m_iOSNotificationSettingsValues;
|
||||
settingsManager.m_AndroidNotificationSettingsValues = settingsManagerLegacy.m_AndroidNotificationSettingsValues;
|
||||
settingsManager.DrawableResources = settingsManagerLegacy.DrawableResources;
|
||||
|
||||
AssetDatabase.DeleteAsset(k_LegacyAssetPath);
|
||||
DeleteEmptyDirectoryAsset("Assets/Editor/com.unity.mobile.notifications");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void DeleteEmptyDirectoryAsset(string directory)
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(directory);
|
||||
if (!directoryInfo.Exists || directoryInfo.GetDirectories().Length > 0 || directoryInfo.GetFiles().Length > 0)
|
||||
return;
|
||||
|
||||
AssetDatabase.DeleteAsset(directory);
|
||||
}
|
||||
|
||||
private T GetOrAddNotificationSettingValue<T>(string key, T defaultValue, bool isAndroid)
|
||||
{
|
||||
var collection = isAndroid ? m_AndroidNotificationSettingsValues : m_iOSNotificationSettingsValues;
|
||||
|
||||
try
|
||||
{
|
||||
var value = collection[key];
|
||||
if (value != null)
|
||||
return (T)value;
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
Debug.LogWarning("Failed loading : " + key + " for type:" + defaultValue.GetType() + " Expected : " + collection[key].GetType());
|
||||
//Just return default value if it's a new setting that was not yet serialized.
|
||||
}
|
||||
|
||||
collection[key] = defaultValue;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public void SaveSetting(NotificationSetting setting, BuildTargetGroup target)
|
||||
{
|
||||
var collection = (target == BuildTargetGroup.Android) ? m_AndroidNotificationSettingsValues : m_iOSNotificationSettingsValues;
|
||||
|
||||
if (!collection.Contains(setting.Key) || collection[setting.Key].ToString() != setting.Value.ToString())
|
||||
{
|
||||
collection[setting.Key] = setting.Value;
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveSettings(bool forceSave = true)
|
||||
{
|
||||
if (!forceSave && File.Exists(k_SettingsPath))
|
||||
return;
|
||||
|
||||
if (AssetDatabase.MakeEditable(k_SettingsPath))
|
||||
File.WriteAllText(k_SettingsPath, EditorJsonUtility.ToJson(this, true));
|
||||
else
|
||||
Debug.LogError($"Failed to make file {k_SettingsPath} editable");
|
||||
}
|
||||
|
||||
public void AddDrawableResource(string id, Texture2D image, NotificationIconType type)
|
||||
{
|
||||
/* commenting out for now, since you can have same Id's in editor
|
||||
foreach (var drawable in DrawableResources)
|
||||
{
|
||||
if (drawable.Id == id)
|
||||
{
|
||||
Debug.LogWarning("Drawable with Id"+id+" already exists, please assign another Id");
|
||||
return;
|
||||
}
|
||||
} */
|
||||
var drawableResource = new DrawableResourceData();
|
||||
drawableResource.Id = id;
|
||||
drawableResource.Type = type;
|
||||
drawableResource.Asset = image;
|
||||
|
||||
DrawableResources.Add(drawableResource);
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public void RemoveDrawableResourceByIndex(int index)
|
||||
{
|
||||
if (index < DrawableResources.Count && index >= 0)
|
||||
{
|
||||
DrawableResources.RemoveAt(index);
|
||||
SaveSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Invalid drawable index provided, drawable not removed.");
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveDrawableResourceById(string id)
|
||||
{
|
||||
DrawableResourceData DrawableRes = null;
|
||||
foreach (var drawable in DrawableResources)
|
||||
{
|
||||
if (drawable.Id == id)
|
||||
{
|
||||
DrawableRes = drawable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (DrawableRes == null)
|
||||
{
|
||||
Debug.LogWarning("Drawable with Id " + id + " not found. Drawable not removed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawableResources.Remove(DrawableRes);
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearDrawableResources()
|
||||
{
|
||||
DrawableResources.Clear();
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public Dictionary<string, byte[]> GenerateDrawableResourcesForExport()
|
||||
{
|
||||
var icons = new Dictionary<string, byte[]>();
|
||||
foreach (var drawableResource in DrawableResources)
|
||||
{
|
||||
if (!drawableResource.Verify())
|
||||
{
|
||||
Debug.LogWarning(string.Format("Failed exporting: '{0}' AndroidSettings notification icon because:\n {1} ",
|
||||
drawableResource.Id,
|
||||
drawableResource.GenerateErrorString()));
|
||||
continue;
|
||||
}
|
||||
|
||||
var texture = TextureAssetUtils.ProcessTextureForType(drawableResource.Asset, drawableResource.Type);
|
||||
|
||||
var scale = drawableResource.Type == NotificationIconType.Small ? 0.375f : 1;
|
||||
|
||||
var textXhdpi = TextureAssetUtils.ScaleTexture(texture, (int)(128 * scale), (int)(128 * scale));
|
||||
var textHdpi = TextureAssetUtils.ScaleTexture(texture, (int)(96 * scale), (int)(96 * scale));
|
||||
var textMdpi = TextureAssetUtils.ScaleTexture(texture, (int)(64 * scale), (int)(64 * scale));
|
||||
var textLdpi = TextureAssetUtils.ScaleTexture(texture, (int)(48 * scale), (int)(48 * scale));
|
||||
|
||||
icons[string.Format("drawable-xhdpi-v11/{0}.png", drawableResource.Id)] = textXhdpi.EncodeToPNG();
|
||||
icons[string.Format("drawable-hdpi-v11/{0}.png", drawableResource.Id)] = textHdpi.EncodeToPNG();
|
||||
icons[string.Format("drawable-mdpi-v11/{0}.png", drawableResource.Id)] = textMdpi.EncodeToPNG();
|
||||
icons[string.Format("drawable-ldpi-v11/{0}.png", drawableResource.Id)] = textLdpi.EncodeToPNG();
|
||||
|
||||
if (drawableResource.Type == NotificationIconType.Large)
|
||||
{
|
||||
var textXxhdpi = TextureAssetUtils.ScaleTexture(texture, (int)(192 * scale), (int)(192 * scale));
|
||||
icons[string.Format("drawable-xxhdpi-v11/{0}.png", drawableResource.Id)] = textXxhdpi.EncodeToPNG();
|
||||
}
|
||||
}
|
||||
|
||||
return icons;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0863bf92b4fcc45b0b9267325249bf0f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using Unity.Notifications.iOS;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
internal class NotificationSettingsProvider : SettingsProvider
|
||||
{
|
||||
private const int k_SlotSize = 64;
|
||||
private const int k_IconSpacing = 8;
|
||||
private const float k_Padding = 12f;
|
||||
private const float k_ToolbarHeight = 20f;
|
||||
private const int k_VerticalSeparator = 2;
|
||||
private const int k_LabelLineHeight = 18;
|
||||
|
||||
private readonly GUIContent k_IdentifierLabelText = new GUIContent("Identifier");
|
||||
private readonly GUIContent k_TypeLabelText = new GUIContent("Type");
|
||||
|
||||
private readonly string[] k_ToolbarStrings = { "Android", "iOS" };
|
||||
private const string k_InfoStringAndroid =
|
||||
"Only icons added to this list or manually added to the 'res/drawable' folder can be used for notifications.\n" +
|
||||
"Note, that not all devices support colored icons.\n\n" +
|
||||
"Small icons must be at least 48x48px and only composed of white pixels on a transparent background.\n" +
|
||||
"Large icons must be no smaller than 192x192px and may contain colors.";
|
||||
|
||||
private NotificationSettingsManager m_SettingsManager;
|
||||
|
||||
private SerializedObject m_SettingsManagerObject;
|
||||
private SerializedProperty m_DrawableResources;
|
||||
|
||||
private Vector2 m_ScrollViewStart;
|
||||
private ReorderableList m_ReorderableList;
|
||||
|
||||
private NotificationSettingsProvider(string path, SettingsScope scopes)
|
||||
: base(path, scopes)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
[SettingsProvider]
|
||||
static SettingsProvider CreateMobileNotificationsSettingsProvider()
|
||||
{
|
||||
return new NotificationSettingsProvider("Project/Mobile Notifications", SettingsScope.Project);
|
||||
}
|
||||
|
||||
public override void OnActivate(string searchContext, VisualElement rootElement)
|
||||
{
|
||||
base.OnActivate(searchContext, rootElement);
|
||||
// in case of domain reload (enter-exit play mode, this gets lost)
|
||||
if (m_SettingsManager == null)
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public override void OnDeactivate()
|
||||
{
|
||||
base.OnDeactivate();
|
||||
m_SettingsManager.SaveSettings(false);
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
label = "Mobile Notifications";
|
||||
m_SettingsManager = NotificationSettingsManager.Initialize();
|
||||
|
||||
// These two are for ReorderableList.
|
||||
m_SettingsManagerObject = new SerializedObject(m_SettingsManager);
|
||||
m_DrawableResources = m_SettingsManagerObject.FindProperty("DrawableResources");
|
||||
|
||||
// ReorderableList is only used to draw the drawable resources for Android settings.
|
||||
InitReorderableList();
|
||||
|
||||
Undo.undoRedoPerformed += () =>
|
||||
{
|
||||
m_SettingsManagerObject.UpdateIfRequiredOrScript();
|
||||
Repaint();
|
||||
};
|
||||
}
|
||||
|
||||
private void InitReorderableList()
|
||||
{
|
||||
m_ReorderableList = new ReorderableList(m_SettingsManagerObject, m_DrawableResources, false, false, true, true);
|
||||
m_ReorderableList.elementHeight = k_SlotSize + k_IconSpacing;
|
||||
m_ReorderableList.showDefaultBackground = true;
|
||||
m_ReorderableList.headerHeight = 1;
|
||||
|
||||
// Register all the necessary callbacks on ReorderableList.
|
||||
m_ReorderableList.onAddCallback = (list) =>
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(m_SettingsManager, "Add a new icon element");
|
||||
m_SettingsManager.AddDrawableResource(string.Format("icon_{0}", m_SettingsManager.DrawableResources.Count), null, NotificationIconType.Small);
|
||||
};
|
||||
|
||||
m_ReorderableList.onRemoveCallback = (list) =>
|
||||
{
|
||||
m_SettingsManager.RemoveDrawableResourceByIndex(list.index);
|
||||
};
|
||||
|
||||
m_ReorderableList.onCanAddCallback = (list) =>
|
||||
{
|
||||
var trackedAssets = m_SettingsManager.DrawableResources;
|
||||
if (trackedAssets.Count <= 0)
|
||||
return true;
|
||||
|
||||
return trackedAssets.All(asset => asset.Initialized());
|
||||
};
|
||||
|
||||
m_ReorderableList.drawElementCallback = (rect, index, active, focused) => DrawIconDataElement(rect, index, active, focused);
|
||||
|
||||
m_ReorderableList.drawElementBackgroundCallback = (rect, index, active, focused) =>
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
var background = index % 2 == 0 ? NotificationStyles.k_EvenRow : NotificationStyles.k_OddRow;
|
||||
background.Draw(rect, false, false, false, false);
|
||||
|
||||
ReorderableList.defaultBehaviours.DrawElementBackground(rect, index, active, focused, true);
|
||||
};
|
||||
|
||||
m_ReorderableList.elementHeightCallback = (index) =>
|
||||
{
|
||||
var data = GetDrawableResource(index);
|
||||
if (data == null)
|
||||
return k_SlotSize;
|
||||
|
||||
return m_ReorderableList.elementHeight + (data.Asset != null && !data.IsValid ? k_SlotSize : 0);
|
||||
};
|
||||
}
|
||||
|
||||
private void DrawIconDataElement(Rect rect, int index, bool active, bool focused)
|
||||
{
|
||||
var drawableResource = GetDrawableResource(index);
|
||||
if (drawableResource == null)
|
||||
return;
|
||||
|
||||
var elementRect = AddPadding(rect, k_Padding, k_Padding / 2);
|
||||
|
||||
// Calculate and draw id and type.
|
||||
var idLabelRect = new Rect(elementRect.x, elementRect.y, k_SlotSize, k_ToolbarHeight);
|
||||
var idTextFieldRect = new Rect(idLabelRect.x + k_SlotSize, idLabelRect.y, k_SlotSize, k_ToolbarHeight);
|
||||
|
||||
var typeLabelRect = new Rect(elementRect.x, elementRect.y + 25, k_SlotSize, k_ToolbarHeight);
|
||||
var typeEnumPopupRect = new Rect(typeLabelRect.x + k_SlotSize, typeLabelRect.y, k_SlotSize, k_ToolbarHeight);
|
||||
|
||||
EditorGUI.LabelField(idLabelRect, k_IdentifierLabelText);
|
||||
var newId = EditorGUI.TextField(idTextFieldRect, drawableResource.Id);
|
||||
|
||||
EditorGUI.LabelField(typeLabelRect, k_TypeLabelText);
|
||||
var newType = (NotificationIconType)EditorGUI.EnumPopup(typeEnumPopupRect, drawableResource.Type);
|
||||
|
||||
// Calculate and draw texture and preview.
|
||||
var textureX = Mathf.Max(elementRect.width - (k_SlotSize * 2 - k_IconSpacing * 5), k_SlotSize * 3);
|
||||
var textureRect = new Rect(textureX, elementRect.y, k_SlotSize, k_SlotSize);
|
||||
var previewTextureRect = new Rect(textureRect.x + k_SlotSize, textureRect.y - 6, k_SlotSize, k_SlotSize);
|
||||
|
||||
var newAsset = (Texture2D)EditorGUI.ObjectField(textureRect, drawableResource.Asset, typeof(Texture2D), false);
|
||||
|
||||
// Check if the texture has been updated.
|
||||
bool updatePreviewTexture = (newId != drawableResource.Id || newType != drawableResource.Type || newAsset != drawableResource.Asset);
|
||||
if (updatePreviewTexture)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(m_SettingsManager, "Update icon data");
|
||||
drawableResource.Id = newId;
|
||||
drawableResource.Type = newType;
|
||||
drawableResource.Asset = newAsset;
|
||||
drawableResource.Clean();
|
||||
drawableResource.Verify();
|
||||
m_SettingsManager.SaveSettings();
|
||||
}
|
||||
|
||||
if (drawableResource.Asset != null && !drawableResource.Verify())
|
||||
{
|
||||
var errorMsgWidth = rect.width - k_Padding * 2;
|
||||
var errorRect = AddPadding(new Rect(elementRect.x, elementRect.y + k_SlotSize, errorMsgWidth, k_LabelLineHeight * 3), 4, 4);
|
||||
|
||||
var errorMsg = "Specified texture can't be used because: \n" + drawableResource.GenerateErrorString();
|
||||
EditorGUI.HelpBox(errorRect, errorMsg, MessageType.Error);
|
||||
|
||||
GUI.Box(previewTextureRect, "Preview not available", NotificationStyles.k_PreviewMessageTextStyle);
|
||||
}
|
||||
else
|
||||
{
|
||||
var previewTexture = drawableResource.GetPreviewTexture(updatePreviewTexture);
|
||||
if (previewTexture != null)
|
||||
{
|
||||
previewTexture.alphaIsTransparency = false;
|
||||
|
||||
EditorGUI.LabelField(previewTextureRect, "Preview", NotificationStyles.k_PreviewLabelTextStyle);
|
||||
|
||||
var previewTextureRectPadded = AddPadding(previewTextureRect, 6f, 6f);
|
||||
previewTextureRectPadded.y += 8;
|
||||
GUI.DrawTexture(previewTextureRectPadded, previewTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DrawableResourceData GetDrawableResource(int index)
|
||||
{
|
||||
var resourceAssets = m_SettingsManager.DrawableResources;
|
||||
if (index < resourceAssets.Count)
|
||||
return resourceAssets[index];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Rect AddPadding(Rect rect, float horizontal, float vertical)
|
||||
{
|
||||
Rect paddingRect = rect;
|
||||
paddingRect.xMin += horizontal;
|
||||
paddingRect.xMax -= horizontal;
|
||||
paddingRect.yMin += vertical;
|
||||
paddingRect.yMax -= vertical;
|
||||
|
||||
return paddingRect;
|
||||
}
|
||||
|
||||
public override void OnGUI(string searchContext)
|
||||
{
|
||||
if (m_SettingsManager == null)
|
||||
Initialize();
|
||||
|
||||
// This has to be called to sync all the changes between m_SettingsManager and m_SettingsManagerObject.
|
||||
if (m_SettingsManagerObject.targetObject != null)
|
||||
m_SettingsManagerObject.Update();
|
||||
|
||||
var noHeightRect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.label, GUILayout.ExpandWidth(true), GUILayout.Height(0));
|
||||
var width = noHeightRect.width;
|
||||
|
||||
var totalRect = new Rect(k_Padding, 0f, width - k_Padding, Screen.height);
|
||||
|
||||
// Draw the toolbar for Android/iOS.
|
||||
var toolBarRect = new Rect(totalRect.x, totalRect.y, totalRect.width, k_ToolbarHeight);
|
||||
var toolbarIndex = GUI.Toolbar(toolBarRect, m_SettingsManager.ToolbarIndex, k_ToolbarStrings);
|
||||
if (toolbarIndex != m_SettingsManager.ToolbarIndex)
|
||||
{
|
||||
m_SettingsManager.ToolbarIndex = toolbarIndex;
|
||||
m_SettingsManager.SaveSettings();
|
||||
}
|
||||
|
||||
var notificationSettingsRect = new Rect(totalRect.x, k_ToolbarHeight + 2, totalRect.width, totalRect.height - k_ToolbarHeight - k_Padding);
|
||||
|
||||
// Draw the notification settings.
|
||||
int drawnSettingsCount = DrawNotificationSettings(notificationSettingsRect, m_SettingsManager.ToolbarIndex);
|
||||
if (drawnSettingsCount <= 0)
|
||||
return;
|
||||
|
||||
float heightPlaceHolder = k_SlotSize;
|
||||
|
||||
// Draw drawable resources list for Android.
|
||||
if (m_SettingsManager.ToolbarIndex == 0)
|
||||
{
|
||||
var iconListlabelRect = new Rect(notificationSettingsRect.x, notificationSettingsRect.y + 85, 180, k_LabelLineHeight);
|
||||
GUI.Label(iconListlabelRect, "Notification Icons", EditorStyles.label);
|
||||
|
||||
// Draw the help message for setting the icons.
|
||||
float labelHeight;
|
||||
if (notificationSettingsRect.width > 510)
|
||||
labelHeight = k_LabelLineHeight * 4;
|
||||
else if (notificationSettingsRect.width > 500)
|
||||
labelHeight = k_LabelLineHeight * 5;
|
||||
else
|
||||
labelHeight = k_LabelLineHeight * 6;
|
||||
var iconListMsgRect = new Rect(iconListlabelRect.x, iconListlabelRect.y + iconListlabelRect.height + k_VerticalSeparator, notificationSettingsRect.width, labelHeight);
|
||||
EditorGUI.SelectableLabel(iconListMsgRect, k_InfoStringAndroid, NotificationStyles.k_IconHelpMessageStyle);
|
||||
|
||||
// Draw the reorderable list for the icon list.
|
||||
var iconListRect = new Rect(iconListMsgRect.x, iconListMsgRect.y + iconListMsgRect.height + k_VerticalSeparator, iconListMsgRect.width, notificationSettingsRect.height - 55f);
|
||||
m_ReorderableList.DoList(iconListRect);
|
||||
|
||||
heightPlaceHolder += iconListMsgRect.height + m_ReorderableList.GetHeight();
|
||||
}
|
||||
|
||||
// We have to do this to occupy the space that ScrollView can set the scrollbars correctly.
|
||||
EditorGUILayout.GetControlRect(true, heightPlaceHolder, GUILayout.MinWidth(width));
|
||||
}
|
||||
|
||||
private int DrawNotificationSettings(Rect rect, int toolbarIndex)
|
||||
{
|
||||
Assert.IsTrue(toolbarIndex == 0 || toolbarIndex == 1);
|
||||
|
||||
List<NotificationSetting> settings;
|
||||
BuildTargetGroup buildTarget;
|
||||
int labelWidthMultiplier;
|
||||
if (toolbarIndex == 0)
|
||||
{
|
||||
settings = m_SettingsManager.AndroidNotificationSettings;
|
||||
buildTarget = BuildTargetGroup.Android;
|
||||
labelWidthMultiplier = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
settings = m_SettingsManager.iOSNotificationSettings;
|
||||
buildTarget = BuildTargetGroup.iOS;
|
||||
labelWidthMultiplier = 5;
|
||||
}
|
||||
|
||||
if (settings == null)
|
||||
return 0;
|
||||
|
||||
GUI.BeginGroup(rect);
|
||||
var drawnSettingsCount = DrawSettingElements(rect, buildTarget, settings, false, 0, labelWidthMultiplier);
|
||||
GUI.EndGroup();
|
||||
|
||||
return drawnSettingsCount;
|
||||
}
|
||||
|
||||
private int DrawSettingElements(Rect rect, BuildTargetGroup buildTarget, List<NotificationSetting> settings, bool disabled, int layer, int labelWidthMultiplier)
|
||||
{
|
||||
var spaceOffset = layer * 13;
|
||||
|
||||
var labelStyle = new GUIStyle(NotificationStyles.k_LabelStyle);
|
||||
labelStyle.fixedWidth = k_SlotSize * labelWidthMultiplier - spaceOffset;
|
||||
|
||||
var toggleStyle = NotificationStyles.k_ToggleStyle;
|
||||
var dropdownStyle = NotificationStyles.k_DropwDownStyle;
|
||||
|
||||
int elementCount = settings.Count;
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(disabled);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.Space(spaceOffset);
|
||||
|
||||
GUILayout.Label(new GUIContent(setting.Label, setting.Tooltip), labelStyle);
|
||||
|
||||
bool dependenciesDisabled = false;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (setting.Value.GetType() == typeof(bool))
|
||||
{
|
||||
setting.Value = EditorGUILayout.Toggle((bool)setting.Value, toggleStyle);
|
||||
dependenciesDisabled = !(bool)setting.Value;
|
||||
}
|
||||
else if (setting.Value.GetType() == typeof(string))
|
||||
{
|
||||
setting.Value = EditorGUILayout.TextField((string)setting.Value);
|
||||
}
|
||||
else if (setting.Value.GetType() == typeof(PresentationOption))
|
||||
{
|
||||
setting.Value = (PresentationOption)EditorGUILayout.EnumFlagsField((iOSPresentationOption)setting.Value, dropdownStyle);
|
||||
}
|
||||
else if (setting.Value.GetType() == typeof(AuthorizationOption))
|
||||
{
|
||||
setting.Value = (AuthorizationOption)EditorGUILayout.EnumFlagsField((iOSAuthorizationOption)setting.Value, dropdownStyle);
|
||||
if ((iOSAuthorizationOption)setting.Value == 0)
|
||||
setting.Value = (AuthorizationOption)(iOSAuthorizationOption.Badge | iOSAuthorizationOption.Sound | iOSAuthorizationOption.Alert);
|
||||
}
|
||||
else if (setting.Value.GetType() == typeof(AndroidExactSchedulingOption))
|
||||
{
|
||||
setting.Value = (AndroidExactSchedulingOption)EditorGUILayout.EnumFlagsField((AndroidExactSchedulingOption)setting.Value, dropdownStyle);
|
||||
}
|
||||
else
|
||||
Debug.LogError("Unsupported setting type: " + setting.Value.GetType());
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_SettingsManager.SaveSetting(setting, buildTarget);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (setting.Dependencies != null)
|
||||
{
|
||||
elementCount += DrawSettingElements(rect, buildTarget, setting.Dependencies, dependenciesDisabled, layer + 1, labelWidthMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
return elementCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8f51a1ea13c0461f8868c187d4ca482
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
internal class NotificationStyles
|
||||
{
|
||||
public static readonly GUIStyle k_EvenRow = new GUIStyle("CN EntryBackEven");
|
||||
public static readonly GUIStyle k_OddRow = new GUIStyle("CN EntryBackOdd");
|
||||
public static readonly GUIStyle k_PreviewMessageTextStyle = new GUIStyle(GUI.skin.label) { fontSize = 8, wordWrap = true, alignment = TextAnchor.MiddleCenter };
|
||||
public static readonly GUIStyle k_PreviewLabelTextStyle = new GUIStyle(GUI.skin.label) { fontSize = 8, wordWrap = true, alignment = TextAnchor.UpperCenter };
|
||||
public static readonly GUIStyle k_IconHelpMessageStyle = new GUIStyle(GUI.skin.GetStyle("HelpBox")) { fontSize = 10, wordWrap = true, alignment = TextAnchor.UpperLeft };
|
||||
public static readonly GUIStyle k_ToggleStyle = new GUIStyle(GUI.skin.GetStyle("Toggle")) { alignment = TextAnchor.MiddleRight };
|
||||
public static readonly GUIStyle k_DropwDownStyle = new GUIStyle(GUI.skin.GetStyle("Button")) { alignment = TextAnchor.MiddleLeft };
|
||||
public static readonly GUIStyle k_LabelStyle = new GUIStyle(GUI.skin.GetStyle("Label")) { wordWrap = true };
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d20e68231df79a46be46dfc6d6e8580
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Vendored
+118
@@ -0,0 +1,118 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
internal static class TextureAssetUtils
|
||||
{
|
||||
public static bool VerifyTextureByType(Texture2D texture, NotificationIconType type, out List<string> errors)
|
||||
{
|
||||
errors = new List<string>();
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
errors.Add("Texture is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
var needsAlpha = true;
|
||||
var minSize = 48;
|
||||
|
||||
if (type == NotificationIconType.Large)
|
||||
{
|
||||
needsAlpha = false;
|
||||
minSize = 192;
|
||||
}
|
||||
|
||||
var isSquare = texture.width == texture.height;
|
||||
var isLargeEnough = texture.width >= minSize;
|
||||
var hasAlpha = true;// TODO: texture.format == TextureFormat.Alpha8;
|
||||
|
||||
var isReadable = texture.isReadable;
|
||||
|
||||
if (!isReadable)
|
||||
{
|
||||
errors.Add("Read/Write is not enabled in the texture importer");
|
||||
}
|
||||
|
||||
if (!isLargeEnough)
|
||||
errors.Add(string.Format("Texture must be at least {0}x{1} pixels (while it's {2}x{3})",
|
||||
minSize,
|
||||
minSize,
|
||||
texture.width,
|
||||
texture.height
|
||||
));
|
||||
|
||||
if (!isSquare)
|
||||
errors.Add(string.Format("Texture must have the same width and height (while it's {0}x{1})",
|
||||
texture.width,
|
||||
texture.height
|
||||
));
|
||||
|
||||
if (!hasAlpha && needsAlpha)
|
||||
errors.Add(string.Format("Texture must contain an alpha channel"));
|
||||
|
||||
return isReadable && isSquare && isLargeEnough && (!needsAlpha || hasAlpha);
|
||||
}
|
||||
|
||||
public static Texture2D ProcessTextureForType(Texture2D sourceTexture, NotificationIconType type)
|
||||
{
|
||||
if (sourceTexture == null)
|
||||
return null;
|
||||
|
||||
string assetPath = AssetDatabase.GetAssetPath(sourceTexture);
|
||||
var importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
|
||||
|
||||
if (importer == null || !importer.isReadable)
|
||||
return null;
|
||||
|
||||
Texture2D texture;
|
||||
if (type == NotificationIconType.Small)
|
||||
{
|
||||
texture = new Texture2D(sourceTexture.width, sourceTexture.height, TextureFormat.RGBA32, true, false);
|
||||
for (var i = 0; i < sourceTexture.mipmapCount; i++)
|
||||
{
|
||||
var c_0 = sourceTexture.GetPixels(i);
|
||||
var c_1 = texture.GetPixels(i);
|
||||
for (var i1 = 0; i1 < c_0.Length; i1++)
|
||||
{
|
||||
var a = c_0[i1].r + c_0[i1].g + c_0[i1].b;
|
||||
c_1[i1].r = c_1[i1].g = c_1[i1].b = a > 127 ? 0 : 1;
|
||||
c_1[i1].a = c_0[i1].a;
|
||||
}
|
||||
texture.SetPixels(c_1, i);
|
||||
}
|
||||
texture.Apply();
|
||||
}
|
||||
else
|
||||
{
|
||||
texture = new Texture2D(sourceTexture.width, sourceTexture.height, TextureFormat.RGBA32, true);
|
||||
texture.SetPixels(sourceTexture.GetPixels());
|
||||
texture.Apply();
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static Texture2D ScaleTexture(Texture2D sourceTexture, int width, int height)
|
||||
{
|
||||
if (sourceTexture.width == width && sourceTexture.height == height)
|
||||
return sourceTexture;
|
||||
|
||||
var result = new Texture2D(width, height, TextureFormat.ARGB32, false);
|
||||
|
||||
var destPixels = new Color[width * height];
|
||||
for (int y = 0; y < height; ++y)
|
||||
{
|
||||
for (int x = 0; x < width; ++x)
|
||||
{
|
||||
destPixels[y * width + x] = sourceTexture.GetPixelBilinear((float)x / (float)width, (float)y / (float)height);
|
||||
}
|
||||
}
|
||||
result.SetPixels(destPixels);
|
||||
result.Apply();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b9bfb8ad48d04ce5af3878b61cb8904
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Unity.Notifications",
|
||||
"references": [
|
||||
"Unity.Notifications.iOS",
|
||||
"Unity.Notifications.Android"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85a7f7b2e1fe24ff2b4c8b5d1a746334
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
[Flags]
|
||||
internal enum iOSAuthorizationOption
|
||||
{
|
||||
Default = 0,
|
||||
Badge = 1 << 0,
|
||||
Sound = 1 << 1,
|
||||
Alert = 1 << 2,
|
||||
CarPlay = 1 << 3,
|
||||
CriticalAlert = 1 << 4,
|
||||
ProvidesAppNotificationSettings = 1 << 5,
|
||||
Provisional = 1 << 6,
|
||||
All = ~0,
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 396d27f7ef9dc419ab240e8ea0135a26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
#if UNITY_IOS
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEditor.iOS.Xcode;
|
||||
using UnityEngine;
|
||||
using Unity.Notifications;
|
||||
using Unity.Notifications.iOS;
|
||||
|
||||
public class iOSNotificationPostProcessor : MonoBehaviour
|
||||
{
|
||||
[PostProcessBuild]
|
||||
public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
|
||||
{
|
||||
if (buildTarget != BuildTarget.iOS)
|
||||
return;
|
||||
|
||||
// Check if we have the minimal iOS version set.
|
||||
bool hasMinOSVersion;
|
||||
try
|
||||
{
|
||||
var requiredVersion = new Version(10, 0);
|
||||
var currentVersion = new Version(PlayerSettings.iOS.targetOSVersionString);
|
||||
hasMinOSVersion = currentVersion >= requiredVersion;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
hasMinOSVersion = false;
|
||||
}
|
||||
|
||||
if (!hasMinOSVersion)
|
||||
Debug.Log("UserNotifications framework is only available on iOS 10.0+, please make sure that you set a correct `Target minimum iOS Version` in Player Settings.");
|
||||
|
||||
var settings = NotificationSettingsManager.Initialize().iOSNotificationSettingsFlat;
|
||||
|
||||
var needLocationFramework = (bool)settings.Find(i => i.Key == NotificationSettings.iOSSettings.USE_LOCATION_TRIGGER).Value;
|
||||
var addPushNotificationCapability = (bool)settings.Find(i => i.Key == NotificationSettings.iOSSettings.ADD_PUSH_CAPABILITY).Value;
|
||||
var addTimeSensitiveEntitlement = (bool)settings.Find(i => i.Key == NotificationSettings.iOSSettings.ADD_TIME_SENSITIVE_ENTITLEMENT).Value;
|
||||
|
||||
var useReleaseAPSEnv = false;
|
||||
if (addPushNotificationCapability)
|
||||
{
|
||||
var useReleaseAPSEnvSetting = settings.Find(i => i.Key == NotificationSettings.iOSSettings.USE_APS_RELEASE);
|
||||
if (useReleaseAPSEnvSetting != null)
|
||||
useReleaseAPSEnv = (bool)useReleaseAPSEnvSetting.Value;
|
||||
}
|
||||
|
||||
PatchPBXProject(path, needLocationFramework, addPushNotificationCapability, useReleaseAPSEnv, addTimeSensitiveEntitlement);
|
||||
PatchPlist(path, settings, addPushNotificationCapability);
|
||||
PatchPreprocessor(path, needLocationFramework, addPushNotificationCapability);
|
||||
}
|
||||
|
||||
private static void PatchPBXProject(string path, bool needLocationFramework, bool addPushNotificationCapability, bool useReleaseAPSEnv, bool addTimeSensitiveEntitlement)
|
||||
{
|
||||
var pbxProjectPath = PBXProject.GetPBXProjectPath(path);
|
||||
|
||||
var needsToWriteChanges = false;
|
||||
|
||||
var pbxProject = new PBXProject();
|
||||
pbxProject.ReadFromString(File.ReadAllText(pbxProjectPath));
|
||||
|
||||
string mainTarget;
|
||||
string unityFrameworkTarget;
|
||||
|
||||
var unityMainTargetGuidMethod = pbxProject.GetType().GetMethod("GetUnityMainTargetGuid");
|
||||
var unityFrameworkTargetGuidMethod = pbxProject.GetType().GetMethod("GetUnityFrameworkTargetGuid");
|
||||
|
||||
if (unityMainTargetGuidMethod != null && unityFrameworkTargetGuidMethod != null)
|
||||
{
|
||||
mainTarget = (string)unityMainTargetGuidMethod.Invoke(pbxProject, null);
|
||||
unityFrameworkTarget = (string)unityFrameworkTargetGuidMethod.Invoke(pbxProject, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
mainTarget = pbxProject.TargetGuidByName("Unity-iPhone");
|
||||
unityFrameworkTarget = mainTarget;
|
||||
}
|
||||
|
||||
// Add necessary frameworks.
|
||||
if (!pbxProject.ContainsFramework(unityFrameworkTarget, "UserNotifications.framework"))
|
||||
{
|
||||
pbxProject.AddFrameworkToProject(unityFrameworkTarget, "UserNotifications.framework", true);
|
||||
needsToWriteChanges = true;
|
||||
}
|
||||
if (needLocationFramework && !pbxProject.ContainsFramework(unityFrameworkTarget, "CoreLocation.framework"))
|
||||
{
|
||||
pbxProject.AddFrameworkToProject(unityFrameworkTarget, "CoreLocation.framework", false);
|
||||
needsToWriteChanges = true;
|
||||
}
|
||||
|
||||
if (needsToWriteChanges)
|
||||
File.WriteAllText(pbxProjectPath, pbxProject.WriteToString());
|
||||
|
||||
var entitlementsFileName = pbxProject.GetBuildPropertyForAnyConfig(mainTarget, "CODE_SIGN_ENTITLEMENTS");
|
||||
if (entitlementsFileName == null)
|
||||
{
|
||||
var bundleIdentifier = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.iOS);
|
||||
entitlementsFileName = string.Format("{0}.entitlements", bundleIdentifier.Substring(bundleIdentifier.LastIndexOf(".") + 1));
|
||||
}
|
||||
|
||||
// Update the entitlements file.
|
||||
if (addPushNotificationCapability)
|
||||
{
|
||||
var capManager = new ProjectCapabilityManager(pbxProjectPath, entitlementsFileName, "Unity-iPhone");
|
||||
capManager.AddPushNotifications(!useReleaseAPSEnv);
|
||||
capManager.WriteToFile();
|
||||
}
|
||||
|
||||
if (addTimeSensitiveEntitlement)
|
||||
{
|
||||
var entitlementsFile = new PlistDocument();
|
||||
var entitlementsFilePath = Path.Combine(path, entitlementsFileName);
|
||||
if (File.Exists(entitlementsFilePath))
|
||||
entitlementsFile.ReadFromFile(entitlementsFilePath);
|
||||
var entitlement = entitlementsFile.root["com.apple.developer.usernotifications.time-sensitive"] as PlistElementBoolean;
|
||||
if (entitlement == null || entitlement.AsBoolean() == false)
|
||||
{
|
||||
entitlementsFile.root["com.apple.developer.usernotifications.time-sensitive"] = new PlistElementBoolean(true);
|
||||
entitlementsFile.WriteToFile(entitlementsFilePath);
|
||||
}
|
||||
if (pbxProject.GetBuildPropertyForAnyConfig(mainTarget, "CODE_SIGN_ENTITLEMENTS") == null)
|
||||
{
|
||||
pbxProject.AddBuildProperty(mainTarget, "CODE_SIGN_ENTITLEMENTS", entitlementsFileName);
|
||||
pbxProject.WriteToFile(pbxProjectPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void PatchPlist(string path, List<Unity.Notifications.NotificationSetting> settings, bool addPushNotificationCapability)
|
||||
{
|
||||
var plistPath = path + "/Info.plist";
|
||||
var plist = new PlistDocument();
|
||||
plist.ReadFromString(File.ReadAllText(plistPath));
|
||||
|
||||
var rootDict = plist.root;
|
||||
var needsToWriteChanges = false;
|
||||
|
||||
// Add all the settings to the plist.
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
if (ShouldAddSettingToPlist(setting, rootDict))
|
||||
{
|
||||
needsToWriteChanges = true;
|
||||
if (setting.Value.GetType() == typeof(bool))
|
||||
{
|
||||
rootDict.SetBoolean(setting.Key, (bool)setting.Value);
|
||||
}
|
||||
else if (setting.Value.GetType() == typeof(PresentationOption) ||
|
||||
setting.Value.GetType() == typeof(AuthorizationOption))
|
||||
{
|
||||
rootDict.SetInteger(setting.Key, (int)setting.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add "remote-notification" to the list of supported UIBackgroundModes.
|
||||
if (addPushNotificationCapability)
|
||||
{
|
||||
PlistElementArray currentBackgroundModes = (PlistElementArray)rootDict["UIBackgroundModes"];
|
||||
if (currentBackgroundModes == null)
|
||||
currentBackgroundModes = rootDict.CreateArray("UIBackgroundModes");
|
||||
|
||||
var remoteNotificationElement = new PlistElementString("remote-notification");
|
||||
if (!currentBackgroundModes.values.Contains(remoteNotificationElement))
|
||||
{
|
||||
currentBackgroundModes.values.Add(remoteNotificationElement);
|
||||
needsToWriteChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsToWriteChanges)
|
||||
File.WriteAllText(plistPath, plist.WriteToString());
|
||||
}
|
||||
|
||||
// If the plist doesn't contain the key, or it's value is different, we should add/overwrite it.
|
||||
private static bool ShouldAddSettingToPlist(Unity.Notifications.NotificationSetting setting,
|
||||
PlistElementDict rootDict)
|
||||
{
|
||||
if (!rootDict.values.ContainsKey(setting.Key))
|
||||
return true;
|
||||
else if (setting.Value.GetType() == typeof(bool))
|
||||
return !rootDict.values[setting.Key].AsBoolean().Equals((bool)setting.Value);
|
||||
else if (setting.Value.GetType() == typeof(PresentationOption) || setting.Value.GetType() == typeof(AuthorizationOption))
|
||||
return !rootDict.values[setting.Key].AsInteger().Equals((int)setting.Value);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void PatchPreprocessor(string path, bool needLocationFramework, bool addPushNotificationCapability)
|
||||
{
|
||||
var preprocessorPath = path + "/Classes/Preprocessor.h";
|
||||
var preprocessor = File.ReadAllText(preprocessorPath);
|
||||
var needsToWriteChanges = false;
|
||||
|
||||
if (needLocationFramework && preprocessor.Contains("UNITY_USES_LOCATION"))
|
||||
{
|
||||
preprocessor = preprocessor.Replace("UNITY_USES_LOCATION 0", "UNITY_USES_LOCATION 1");
|
||||
needsToWriteChanges = true;
|
||||
}
|
||||
|
||||
if (addPushNotificationCapability && preprocessor.Contains("UNITY_USES_REMOTE_NOTIFICATIONS"))
|
||||
{
|
||||
preprocessor =
|
||||
preprocessor.Replace("UNITY_USES_REMOTE_NOTIFICATIONS 0", "UNITY_USES_REMOTE_NOTIFICATIONS 1");
|
||||
needsToWriteChanges = true;
|
||||
}
|
||||
|
||||
if (needsToWriteChanges)
|
||||
File.WriteAllText(preprocessorPath, preprocessor);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53cc9fd7c6f7b49f89ceebacdbc87512
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
[Flags]
|
||||
internal enum iOSPresentationOption
|
||||
{
|
||||
Badge = 1 << 0,
|
||||
Sound = 1 << 1,
|
||||
Alert = 1 << 2,
|
||||
List = 1 << 3,
|
||||
Banner = 1 << 4,
|
||||
All = ~0,
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5fe6a884940b415f94f2b501eff5ce9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
Mobile Notifications copyright © 2020 Unity Technologies ApS
|
||||
|
||||
Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License).
|
||||
|
||||
Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b120cb2074d184c898403ffcb68b1172
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5b0690debfe24ec5a3c76b61a6cc12a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3040eaa3b547042fd9ee979fee321768
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+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:
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50ed07b40a2b46e46a7f0ff1874fcde9
|
||||
guid: b4d5e3b0fd3074704a1c4fcee255f85c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
+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:
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2928de7141e034d699719cb63e6156e9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
|
||||
#if UNITY_ANDROID
|
||||
using PlatformNotification = Unity.Notifications.Android.AndroidNotification;
|
||||
#else
|
||||
using System.Globalization;
|
||||
using PlatformNotification = Unity.Notifications.iOS.iOSNotification;
|
||||
#endif
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a notification to be sent or a received one.
|
||||
/// Can be converted to platform specific notification via explicit cast.
|
||||
/// <code>
|
||||
/// var n1 = (AndroidNotification)notification; // convert to Android
|
||||
/// var n1 = (iOSNotification)notification; // convert to iOS
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public struct Notification
|
||||
{
|
||||
PlatformNotification notification;
|
||||
|
||||
public static explicit operator PlatformNotification(Notification n)
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
n.notification.ShowInForeground = n.ShowInForeground;
|
||||
n.notification.ShouldAutoCancel = true; // iOS always auto-cancels
|
||||
return n.notification;
|
||||
#else
|
||||
var ret = n.notification;
|
||||
if (ret != null && n.Identifier.HasValue)
|
||||
ret.Identifier = n.Identifier.Value.ToString(CultureInfo.InvariantCulture);
|
||||
ret.ShowInForeground = n.ShowInForeground;
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
internal Notification(PlatformNotification notification
|
||||
#if UNITY_ANDROID
|
||||
, int id
|
||||
#endif
|
||||
)
|
||||
{
|
||||
this.notification = notification;
|
||||
Identifier = default;
|
||||
ShowInForeground = notification.ShowInForeground;
|
||||
|
||||
#if UNITY_ANDROID
|
||||
Identifier = id;
|
||||
#else
|
||||
if (int.TryParse(notification.Identifier, NumberStyles.None, CultureInfo.InvariantCulture, out int val))
|
||||
Identifier = val;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !UNITY_ANDROID // so code compiles when something other than Android/iOS is selected
|
||||
PlatformNotification GetNotification()
|
||||
{
|
||||
if (notification == null)
|
||||
notification = new PlatformNotification();
|
||||
return notification;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// A unique identifier for this notification.
|
||||
/// If null, a unique ID will be generated when scheduling.
|
||||
/// </summary>
|
||||
public int? Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// String that is shown on notification as title.
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_IOS
|
||||
if (notification == null)
|
||||
return null;
|
||||
#endif
|
||||
return notification.Title;
|
||||
}
|
||||
set
|
||||
{
|
||||
#if UNITY_IOS
|
||||
if (notification == null)
|
||||
notification = new PlatformNotification();
|
||||
#endif
|
||||
notification.Title = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String that is shown on notification as it's main body.
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
return notification.Text;
|
||||
#else
|
||||
return notification?.Body;
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
notification.Text = value;
|
||||
#else
|
||||
GetNotification().Body = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arbitrary data that is sent with notification.
|
||||
/// Can be used to store some useful information in the notification to be later retrieved when notification arrives or is tapped by user.
|
||||
/// </summary>
|
||||
public string Data
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
return notification.IntentData;
|
||||
#else
|
||||
return notification?.Data;
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
notification.IntentData = value;
|
||||
#else
|
||||
GetNotification().Data = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number, associated with the notification. Zero is ignored.
|
||||
/// When supported, shows up as badge on application launcher.
|
||||
/// </summary>
|
||||
public int Badge
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
return notification.Number;
|
||||
#else
|
||||
return notification == null ? 0 : notification.Badge;
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
notification.Number = value;
|
||||
#else
|
||||
GetNotification().Badge = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Default value differs on Android/iOS, so have separate here and unify uppon conversion
|
||||
/// <summary>
|
||||
/// Indicated, whether notification should be shown if it arrives while application is in foreground.
|
||||
/// When notification arrives with app in foreground <see cref="NotificationCenter.OnNotificationReceived"/> even fires regardless of this.
|
||||
/// Default is false, meaning notifications are silent when app is in foreground.
|
||||
/// </summary>
|
||||
public bool ShowInForeground { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for the group this notification belong to.
|
||||
/// If device supports it, notifications with same group identifier are stacked together.
|
||||
/// On Android this is also called group, while on iOS it is called thread identidier.
|
||||
/// </summary>
|
||||
public string Group
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
return notification.Group;
|
||||
#else
|
||||
return notification?.ThreadIdentifier;
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
notification.Group = value;
|
||||
#else
|
||||
GetNotification().ThreadIdentifier = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this notification as group summary.
|
||||
/// Only has effect if <see cref="Group"/> is also set.
|
||||
/// Android only. On iOS will be just another notification in the group.
|
||||
/// </summary>
|
||||
public bool IsGroupSummary
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
return notification.GroupSummary;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
notification.GroupSummary = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c21b2456627d40629aab46fbb054de9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+423
@@ -0,0 +1,423 @@
|
||||
using System;
|
||||
|
||||
#if UNITY_ANDROID
|
||||
using Unity.Notifications.Android;
|
||||
#else
|
||||
using System.Globalization;
|
||||
using Unity.Notifications.iOS;
|
||||
#endif
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Options, specifying how notifications should be presented to the user.
|
||||
/// These option can be bitwise-ored to combine them.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// On Android Alert and Sound flags are used to choose importance level for the channel, so specifying Alert also includes sound.
|
||||
/// </remarks>
|
||||
/// <seealso cref="NotificationCenterArgs.PresentationOptions"/>
|
||||
[Flags]
|
||||
public enum NotificationPresentation
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that when notification arrives, a pop-up should show up on screen.
|
||||
/// Alerts can be disabled by user in device settings. They may also be disabled by default.
|
||||
/// </summary>
|
||||
Alert = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Whether notifications can set a badge on applications launcher.
|
||||
/// </summary>
|
||||
Badge = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Whether notifications cause device to play sound uppon arrival.
|
||||
/// </summary>
|
||||
Sound = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Causes device to vibrate when notification is received. Android only.
|
||||
/// </summary>
|
||||
Vibrate = 1 << 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The settings section to open, if possible.
|
||||
/// </summary>
|
||||
public enum NotificationSettingsSection
|
||||
{
|
||||
/// <summary>
|
||||
/// Opens settings for application and tries to open the section for notifications.
|
||||
/// </summary>
|
||||
Application,
|
||||
|
||||
/// <summary>
|
||||
/// Tries to navigate to section for a particular notification category.
|
||||
/// Since Android 8.0 will open notification settings for the specific notification channel.
|
||||
/// </summary>
|
||||
Category,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialization arguments for <see cref="NotificationCenter"/>.
|
||||
/// Recommended to use <see cref="Default"/> to retrieve recommened default values and then alter it.
|
||||
/// It is required to manually set <see cref="AndroidChannelId"/>.
|
||||
/// </summary>
|
||||
public struct NotificationCenterArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns recommended default values for all settings.
|
||||
/// </summary>
|
||||
public static NotificationCenterArgs Default => new NotificationCenterArgs()
|
||||
{
|
||||
PresentationOptions = NotificationPresentation.Badge | NotificationPresentation.Sound,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Options specifying how notifications should be presented to user when they arrive.
|
||||
/// On Android these only have effect if notification channel is created uppon initialization (all channel related properties are set).
|
||||
/// On iOS these only have effect if permission to post notification is later requested using <see cref="NotificationCenter.RequestPermission"/>
|
||||
/// </summary>
|
||||
public NotificationPresentation PresentationOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A custom non-empty string to identify notification channel. Required, Android only.
|
||||
/// All notifications will be sent to this channel.
|
||||
/// Channel is created automatically during initialization if <see cref="AndroidChannelName"/> and <see cref="AndroidChannelDescription"/> are both set.
|
||||
/// If name and description are left null, channel with given identifier has to be created manually (for example using <see cref="AndroidNotificationCenter.RegisterNotificationChannel(AndroidNotificationChannel)"/>).
|
||||
/// </summary>
|
||||
public string AndroidChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A user visible name for notification channel. Optional, Android only.
|
||||
/// Leave null, if you wish to create channel manually.
|
||||
/// Set this to channel name for it to be created automatically during initialization.
|
||||
/// If this is set, then <see cref="AndroidChannelDescription"/> must also be set.
|
||||
/// </summary>
|
||||
/// <seealso cref="AndroidChannelId"/>
|
||||
public string AndroidChannelName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A user visible description for the channel. Optional, Android only.
|
||||
/// Leave null, if you wish to create channel manually.
|
||||
/// Set this to channel description for it to be created automatically during initialization.
|
||||
/// If this is set, then <see cref="AndroidChannelName"/> must also be set.
|
||||
/// </summary>
|
||||
/// <seealso cref="AndroidChannelId"/>
|
||||
public string AndroidChannelDescription { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send, receive and manage notifications.
|
||||
/// Must be initialized before use. See <see cref="Initialize(NotificationCenterArgs)"/>.
|
||||
/// </summary>
|
||||
public static class NotificationCenter
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for <see cref="OnNotificationReceived"/>.
|
||||
/// </summary>
|
||||
/// <param name="notification">Notification that has been received.</param>
|
||||
public delegate void NotificationReceivedCallback(Notification notification);
|
||||
|
||||
static bool s_Initialized = false;
|
||||
static NotificationCenterArgs s_Args;
|
||||
static event NotificationReceivedCallback s_OnNotificationReceived;
|
||||
static bool s_OnNotificationReceivedSet = false;
|
||||
|
||||
#if UNITY_ANDROID
|
||||
static void NotificationReceived(AndroidNotificationIntentData data)
|
||||
#else
|
||||
static void NotificationReceived(iOSNotification notif)
|
||||
#endif
|
||||
{
|
||||
if (s_OnNotificationReceived != null)
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
var notification = new Notification(data.Notification, data.Id);
|
||||
#else
|
||||
var notification = new Notification(notif);
|
||||
#endif
|
||||
|
||||
s_OnNotificationReceived(notification);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event that fires when notification is received.
|
||||
/// Application must be running for this event to work.
|
||||
/// On Android this even fires if application is in foreground or when it's brought back from background.
|
||||
/// On iOS this even only fires when notification is received while app is in foreground.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event is the same as AndroidNotificationCenter.OnNotificationReceived and iOSNotificationCenter.OnNotificationReceived.
|
||||
/// </remarks>
|
||||
public static event NotificationReceivedCallback OnNotificationReceived
|
||||
{
|
||||
add
|
||||
{
|
||||
if (!s_OnNotificationReceivedSet)
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
AndroidNotificationCenter.OnNotificationReceived += NotificationReceived;
|
||||
#else
|
||||
iOSNotificationCenter.OnNotificationReceived += NotificationReceived;
|
||||
#endif
|
||||
s_OnNotificationReceivedSet = true;
|
||||
}
|
||||
s_OnNotificationReceived += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
s_OnNotificationReceived -= value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes notification center with given arguments.
|
||||
/// On Android it will also create notification channel if arguments are set accordingly.
|
||||
/// </summary>
|
||||
/// <param name="args">Arguments for initialization.</param>
|
||||
public static void Initialize(NotificationCenterArgs args)
|
||||
{
|
||||
if (s_Initialized)
|
||||
return;
|
||||
if (string.IsNullOrEmpty(args.AndroidChannelId))
|
||||
throw new ArgumentException("AndroidChannel not provided");
|
||||
|
||||
s_Args = args;
|
||||
s_Initialized = true;
|
||||
|
||||
#if UNITY_ANDROID
|
||||
AndroidNotificationCenter.Initialize();
|
||||
if (args.AndroidChannelName != null || args.AndroidChannelDescription != null)
|
||||
{
|
||||
Importance importance = Importance.Low;
|
||||
if (0 != (args.PresentationOptions & NotificationPresentation.Alert))
|
||||
importance = Importance.High;
|
||||
else if (0 != (args.PresentationOptions & NotificationPresentation.Sound))
|
||||
importance = Importance.Default;
|
||||
|
||||
AndroidNotificationCenter.RegisterNotificationChannel(new AndroidNotificationChannel()
|
||||
{
|
||||
Id = args.AndroidChannelId,
|
||||
Name = args.AndroidChannelName,
|
||||
Description = args.AndroidChannelDescription,
|
||||
Importance = importance,
|
||||
CanShowBadge = 0 != (args.PresentationOptions & NotificationPresentation.Badge),
|
||||
EnableVibration = 0 != (args.PresentationOptions & NotificationPresentation.Vibrate),
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for users permission to post notifications.
|
||||
/// Requests users permission to send notifications. Unless already allowed or permanently denied, shows UI to the user.
|
||||
/// Users can revoke permission in Settings while app is not running.
|
||||
/// </summary>
|
||||
/// <returns>An object for tracking the request and obtaining results.</returns>
|
||||
/// <remarks>Before Android 13 no permission is required.</remarks>
|
||||
public static NotificationsPermissionRequest RequestPermission()
|
||||
{
|
||||
CheckInitialized();
|
||||
|
||||
int iOSAuthorizationOptions = 0;
|
||||
#if UNITY_IOS
|
||||
if (0 != (s_Args.PresentationOptions & NotificationPresentation.Alert))
|
||||
iOSAuthorizationOptions |= (int)AuthorizationOption.Alert;
|
||||
if (0 != (s_Args.PresentationOptions & NotificationPresentation.Badge))
|
||||
iOSAuthorizationOptions |= (int)AuthorizationOption.Badge;
|
||||
if (0 != (s_Args.PresentationOptions & NotificationPresentation.Sound))
|
||||
iOSAuthorizationOptions |= (int)AuthorizationOption.Sound;
|
||||
#endif
|
||||
return new NotificationsPermissionRequest(iOSAuthorizationOptions);
|
||||
}
|
||||
|
||||
static void CheckInitialized()
|
||||
{
|
||||
if (!s_Initialized)
|
||||
throw new Exception("NotificationCenter not initialized");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule notification to be shown in the future.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the schedule, usually deduced from actually passed one.</typeparam>
|
||||
/// <param name="notification">Notification to send.</param>
|
||||
/// <param name="schedule">Schedule, specifying, when notification should be shown.</param>
|
||||
/// <returns>Notification identifier.</returns>
|
||||
public static int ScheduleNotification<T>(Notification notification, T schedule)
|
||||
where T : NotificationSchedule
|
||||
{
|
||||
string category = null;
|
||||
#if UNITY_ANDROID
|
||||
category = s_Args.AndroidChannelId;
|
||||
#endif
|
||||
return ScheduleNotification(notification, category, schedule);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule notification to be shown in the future.
|
||||
/// Allows to explicitly specify the category to send notification to. On Android it is notification channel.
|
||||
/// Channel or category has to be created manually using AndroidNotificationCenter and iOSNotificationCenter respectively.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the schedule, usually deduced from actually passed one.</typeparam>
|
||||
/// <param name="notification">Notification to send.</param>
|
||||
/// <param name="category">Identifier for iOS category or Android channel.</param>
|
||||
/// <param name="schedule">Schedule, specifying, when notification should be shown.</param>
|
||||
/// <returns>Notification identifier.</returns>
|
||||
public static int ScheduleNotification<T>(Notification notification, string category, T schedule)
|
||||
where T : NotificationSchedule
|
||||
{
|
||||
CheckInitialized();
|
||||
|
||||
#if UNITY_ANDROID
|
||||
var n = (AndroidNotification)notification;
|
||||
schedule.Schedule(ref n);
|
||||
if (notification.Identifier.HasValue)
|
||||
{
|
||||
AndroidNotificationCenter.SendNotificationWithExplicitID(n, category, notification.Identifier.Value);
|
||||
return notification.Identifier.Value;
|
||||
}
|
||||
else
|
||||
return AndroidNotificationCenter.SendNotification(n, category);
|
||||
#else
|
||||
var n = (iOSNotification)notification;
|
||||
if (n == null)
|
||||
throw new ArgumentException("Passed notifiation is empty");
|
||||
n.CategoryIdentifier = category;
|
||||
schedule.Schedule(ref n);
|
||||
iOSNotificationCenter.ScheduleNotification(n);
|
||||
if (notification.Identifier.HasValue)
|
||||
return notification.Identifier.Value;
|
||||
// iOSNotification is class and has auto-generated id set at this point
|
||||
// for consistency with Android set it back to null, so same Notification can be sent again as new one
|
||||
int id = int.Parse(n.Identifier, NumberStyles.None, CultureInfo.InvariantCulture);
|
||||
n.Identifier = null;
|
||||
return id;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns last notification tapped by user, or null.
|
||||
/// </summary>
|
||||
public static Notification? LastRespondedNotification
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckInitialized();
|
||||
|
||||
#if UNITY_ANDROID
|
||||
var intent = AndroidNotificationCenter.GetLastNotificationIntent();
|
||||
if (intent == null)
|
||||
return null;
|
||||
return new Notification(intent.Notification, intent.Id);
|
||||
#else
|
||||
var notification = iOSNotificationCenter.GetLastRespondedNotification();
|
||||
if (notification == null)
|
||||
return null;
|
||||
return new Notification(notification);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel a scheduled notification.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the notification to cancel.</param>
|
||||
public static void CancelScheduledNotification(int id)
|
||||
{
|
||||
CheckInitialized();
|
||||
|
||||
#if UNITY_ANDROID
|
||||
AndroidNotificationCenter.CancelScheduledNotification(id);
|
||||
#else
|
||||
iOSNotificationCenter.RemoveScheduledNotification(id.ToString(CultureInfo.InvariantCulture));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel delivered notification.
|
||||
/// Removes notification from the tray.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the notification to cancel.</param>
|
||||
public static void CancelDeliveredNotification(int id)
|
||||
{
|
||||
CheckInitialized();
|
||||
|
||||
#if UNITY_ANDROID
|
||||
AndroidNotificationCenter.CancelDisplayedNotification(id);
|
||||
#else
|
||||
iOSNotificationCenter.RemoveDeliveredNotification(id.ToString(CultureInfo.InvariantCulture));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel all future notifications.
|
||||
/// </summary>
|
||||
public static void CancelAllScheduledNotifications()
|
||||
{
|
||||
CheckInitialized();
|
||||
|
||||
#if UNITY_ANDROID
|
||||
AndroidNotificationCenter.CancelAllScheduledNotifications();
|
||||
#else
|
||||
iOSNotificationCenter.RemoveAllScheduledNotifications();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all already delivered notifications.
|
||||
/// </summary>
|
||||
public static void CancelAllDeliveredNotifications()
|
||||
{
|
||||
CheckInitialized();
|
||||
|
||||
#if UNITY_ANDROID
|
||||
AndroidNotificationCenter.CancelAllDisplayedNotifications();
|
||||
#else
|
||||
iOSNotificationCenter.RemoveAllDeliveredNotifications();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear Application badge. iOS only.
|
||||
/// iOS applications can set numeric badge on app icon. Calling this method removes that badge.
|
||||
/// On Android badge is removed automatically when notifications are removed.
|
||||
/// </summary>
|
||||
public static void ClearBadge()
|
||||
{
|
||||
CheckInitialized();
|
||||
|
||||
#if UNITY_IOS
|
||||
iOSNotificationCenter.ApplicationBadge = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens settings for the application.
|
||||
/// If possible, will try to navigate as close to requested section as it can.
|
||||
/// On iOS and Android prior to 8.0 will open settings for the application.
|
||||
/// Since Android 8.0 will open notification settings for either application or the default channel.
|
||||
/// </summary>
|
||||
/// <param name="section">The section to navigate to.</param>
|
||||
public static void OpenNotificationSettings(NotificationSettingsSection section = NotificationSettingsSection.Application)
|
||||
{
|
||||
CheckInitialized();
|
||||
|
||||
#if UNITY_ANDROID
|
||||
string channel = section switch
|
||||
{
|
||||
NotificationSettingsSection.Category => s_Args.AndroidChannelId,
|
||||
NotificationSettingsSection.Application => null,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
AndroidNotificationCenter.OpenNotificationSettings(channel);
|
||||
#else
|
||||
iOSNotificationCenter.OpenNotificationSettings();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89f50368f285b453ebeac8f965391651
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
#if UNITY_ANDROID
|
||||
using PlatformNotification = Unity.Notifications.Android.AndroidNotification;
|
||||
#else
|
||||
using Unity.Notifications.iOS;
|
||||
using PlatformNotification = Unity.Notifications.iOS.iOSNotification;
|
||||
#endif
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Interval, at which notification should repeat.
|
||||
/// </summary>
|
||||
public enum NotificationRepeatInterval
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates, that notification does not repeat.
|
||||
/// </summary>
|
||||
OneTime = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates, that notification should repeat daily.
|
||||
/// When used in <see cref="NotificationDateTimeSchedule"/>, only time is used for scheduling.
|
||||
/// </summary>
|
||||
Daily = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marker interface for different schedule types.
|
||||
/// </summary>
|
||||
public interface NotificationSchedule
|
||||
{
|
||||
internal void Schedule(ref PlatformNotification notification);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule notification to show up after a certain amount of time, optionally repeating at the same time interval.
|
||||
/// </summary>
|
||||
public struct NotificationIntervalSchedule
|
||||
: NotificationSchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// Time interval to show notification from current time.
|
||||
/// Only full seconds are considered.
|
||||
/// </summary>
|
||||
public TimeSpan Interval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether notification should repeat.
|
||||
/// If true, notification will repeat at the same interval as initial time from the current one.
|
||||
/// </summary>
|
||||
public bool Repeats { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Convenience constructor.
|
||||
/// </summary>
|
||||
/// <param name="interval">Value for <see cref="Interval"/></param>
|
||||
/// <param name="repeats">Value for <see cref="Repeats"/></param>
|
||||
public NotificationIntervalSchedule(TimeSpan interval, bool repeats = false)
|
||||
{
|
||||
Interval = interval;
|
||||
Repeats = repeats;
|
||||
}
|
||||
|
||||
void NotificationSchedule.Schedule(ref PlatformNotification notification)
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
notification.FireTime = DateTime.Now + Interval;
|
||||
if (Repeats)
|
||||
notification.RepeatInterval = Interval;
|
||||
#else
|
||||
notification.Trigger = new iOSNotificationTimeIntervalTrigger()
|
||||
{
|
||||
TimeInterval = Interval,
|
||||
Repeats = Repeats,
|
||||
};
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule to show notification at particular date and time.
|
||||
/// Optionally can repeat at predefined intervals.
|
||||
/// </summary>
|
||||
public struct NotificationDateTimeSchedule
|
||||
: NotificationSchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// Date and time when notification has to be shown if does not repeat.
|
||||
/// If notification is set to repeat, the meaning of this value depends on <see cref="NotificationRepeatInterval"/>.
|
||||
/// </summary>
|
||||
public DateTime FireTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Interval, at which notification should repeat from the first delivery.
|
||||
/// </summary>
|
||||
public NotificationRepeatInterval RepeatInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Convenience constructor.
|
||||
/// </summary>
|
||||
/// <param name="fireTime">Value for <see cref="FireTime"/></param>
|
||||
/// <param name="repeatInterval">Value for <see cref="RepeatInterval"/></param>
|
||||
public NotificationDateTimeSchedule(DateTime fireTime, NotificationRepeatInterval repeatInterval = NotificationRepeatInterval.OneTime)
|
||||
{
|
||||
FireTime = fireTime;
|
||||
RepeatInterval = repeatInterval;
|
||||
}
|
||||
|
||||
void NotificationSchedule.Schedule(ref PlatformNotification notification)
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
// TODO handle UTC
|
||||
switch (RepeatInterval)
|
||||
{
|
||||
case NotificationRepeatInterval.OneTime:
|
||||
notification.FireTime = FireTime;
|
||||
break;
|
||||
case NotificationRepeatInterval.Daily:
|
||||
{
|
||||
var currentTime = DateTime.Now;
|
||||
var fireTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, FireTime.Hour, FireTime.Minute, FireTime.Second);
|
||||
if (fireTime < currentTime)
|
||||
fireTime = fireTime.AddDays(1);
|
||||
notification.FireTime = fireTime;
|
||||
notification.RepeatInterval = TimeSpan.FromDays(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
var trigger = new iOSNotificationCalendarTrigger()
|
||||
{
|
||||
Hour = FireTime.Hour,
|
||||
Minute = FireTime.Minute,
|
||||
Second = FireTime.Second,
|
||||
UtcTime = FireTime.Kind == DateTimeKind.Utc,
|
||||
};
|
||||
|
||||
switch (RepeatInterval)
|
||||
{
|
||||
case NotificationRepeatInterval.OneTime:
|
||||
trigger.Year = FireTime.Year;
|
||||
trigger.Month = FireTime.Month;
|
||||
trigger.Day = FireTime.Day;
|
||||
break;
|
||||
case NotificationRepeatInterval.Daily:
|
||||
trigger.Day = null;
|
||||
trigger.Repeats = true;
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Unsupported repeat interval {RepeatInterval}");
|
||||
}
|
||||
|
||||
notification.Trigger = trigger;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 304ff37de66064f79ad81cb7991a4b36
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_ANDROID
|
||||
using Unity.Notifications.Android;
|
||||
#else
|
||||
using Unity.Notifications.iOS;
|
||||
#endif
|
||||
|
||||
namespace Unity.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// The status of notification permission request.
|
||||
/// </summary>
|
||||
/// <seealso cref="NotificationsPermissionRequest.Status"/>
|
||||
public enum NotificationsPermissionStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that request is ongoing. Usually mean that user is presented with UI to respond to.
|
||||
/// </summary>
|
||||
RequestPending,
|
||||
|
||||
/// <summary>
|
||||
/// Permission granted, you can post notifications.
|
||||
/// </summary>
|
||||
Granted,
|
||||
|
||||
/// <summary>
|
||||
/// Permission denied, sending notifications will not work.
|
||||
/// </summary>
|
||||
Denied,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Track the status of notification permission request.
|
||||
/// Can be returned from coroutine to suspend it until request is either granted or denied.
|
||||
/// Permission can be granted or denied immediately, if this isn't the first request.
|
||||
/// </summary>
|
||||
public class NotificationsPermissionRequest
|
||||
: CustomYieldInstruction
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
PermissionRequest request;
|
||||
#else
|
||||
AuthorizationRequest request;
|
||||
#endif
|
||||
|
||||
internal NotificationsPermissionRequest(int options)
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
// do not create request if already allowed
|
||||
if (AndroidNotificationCenter.UserPermissionToPost != PermissionStatus.Allowed)
|
||||
request = new PermissionRequest();
|
||||
#else
|
||||
request = new AuthorizationRequest((AuthorizationOption)options, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridden property of base class. Indicates if coroutine should be suspended.
|
||||
/// </summary>
|
||||
public override bool keepWaiting => (request == null)
|
||||
? false
|
||||
#if UNITY_ANDROID
|
||||
: request.Status == PermissionStatus.RequestPending;
|
||||
#else
|
||||
: !request.IsFinished;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Returns a status of this request.
|
||||
/// </summary>
|
||||
public NotificationsPermissionStatus Status
|
||||
{
|
||||
get
|
||||
{
|
||||
if (request == null)
|
||||
return NotificationsPermissionStatus.Granted;
|
||||
|
||||
#if UNITY_ANDROID
|
||||
return request.Status switch
|
||||
{
|
||||
PermissionStatus.RequestPending => NotificationsPermissionStatus.RequestPending,
|
||||
PermissionStatus.Allowed => NotificationsPermissionStatus.Granted,
|
||||
_ => NotificationsPermissionStatus.Denied,
|
||||
};
|
||||
#else
|
||||
if (!request.IsFinished)
|
||||
return NotificationsPermissionStatus.RequestPending;
|
||||
return request.Granted ? NotificationsPermissionStatus.Granted : NotificationsPermissionStatus.Denied;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf48a9bb20f8b4cdbbf92393fd680077
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Unity.Notifications.Unified",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"Unity.Notifications.Android",
|
||||
"Unity.Notifications.iOS"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Android",
|
||||
"Editor",
|
||||
"iOS"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d71139be5ab9e4bb49bc95f8093014e9
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00c2f1475f1804a4e8559419f485be7b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Notifications.Tests")]
|
||||
[assembly: InternalsVisibleTo("Unity.iOS.Notifications.Tests")]
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eaec56e278645461f8385f7af3c99e8a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Notifications.iOS
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum for requesting authorization to interact with the user.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum AuthorizationOption
|
||||
{
|
||||
/// <summary>
|
||||
/// The ability to update the app’s badge.
|
||||
/// </summary>
|
||||
Badge = (1 << 0),
|
||||
|
||||
/// <summary>
|
||||
/// The ability to play sounds.
|
||||
/// </summary>
|
||||
Sound = (1 << 1),
|
||||
|
||||
/// <summary>
|
||||
/// The ability to display alerts.
|
||||
/// </summary>
|
||||
Alert = (1 << 2),
|
||||
|
||||
/// <summary>
|
||||
/// The ability to display notifications in a CarPlay environment.
|
||||
/// </summary>
|
||||
CarPlay = (1 << 3),
|
||||
|
||||
/// <summary>
|
||||
/// The ability to play sounds for critical alerts.
|
||||
/// Critical alerts require a special entitlement issued by Apple.
|
||||
/// </summary>
|
||||
CriticalAlert = (1 << 4),
|
||||
|
||||
/// <summary>
|
||||
/// An option indicating the system should display a button for in-app notification settings.
|
||||
/// </summary>
|
||||
ProvidesAppNotificationSettings = (1 << 5),
|
||||
|
||||
/// <summary>
|
||||
/// The ability to post noninterrupting notifications provisionally to the Notification Center.
|
||||
/// </summary>
|
||||
Provisional = (1 << 6),
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct iOSAuthorizationRequestData
|
||||
{
|
||||
internal int granted;
|
||||
internal string error;
|
||||
internal string deviceToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this to request authorization to interact with the user when you with to deliver local and remote notifications are delivered to the user's device.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method must be called before you attempt to schedule any local notifications. If "Request Authorization on App Launch" is enabled in
|
||||
/// "Edit -> Project Settings -> Mobile Notification Settings" this method will be called automatically when the app launches. You might call this method again to determine the current
|
||||
/// authorizations status or retrieve the DeviceToken for Push Notifications. However the UI system prompt will not be shown if the user has already granted or denied authorization for this app.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// using (var req = new AuthorizationRequest(AuthorizationOption.Alert | AuthorizationOption.Badge, true))
|
||||
/// {
|
||||
/// while (!req.IsFinished)
|
||||
/// {
|
||||
/// yield return null;
|
||||
/// };
|
||||
///
|
||||
/// string result = "\n RequestAuthorization: \n";
|
||||
/// result += "\n finished: " + req.IsFinished;
|
||||
/// result += "\n granted : " + req.Granted;
|
||||
/// result += "\n error: " + req.Error;
|
||||
/// result += "\n deviceToken: " + req.DeviceToken;
|
||||
/// Debug.Log(res);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class AuthorizationRequest : IDisposable
|
||||
{
|
||||
bool m_IsFinished;
|
||||
bool m_Granted;
|
||||
string m_Error;
|
||||
string m_DeviceToken;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the authorization request has completed.
|
||||
/// </summary>
|
||||
public bool IsFinished
|
||||
{
|
||||
get { lock (this) { return m_IsFinished; } }
|
||||
private set { m_IsFinished = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A property indicating whether authorization was granted. The value of this parameter is set to true when authorization was granted for one or more options. The value is set to false when authorization is denied for all options.
|
||||
/// </summary>
|
||||
public bool Granted
|
||||
{
|
||||
get { lock (this) { return m_Granted; } }
|
||||
private set { m_Granted = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains error information of the request failed for some reason or an empty string if no error occurred.
|
||||
/// </summary>
|
||||
public string Error
|
||||
{
|
||||
get { lock (this) { return m_Error; } }
|
||||
private set { m_Error = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A globally unique token that identifies this device to Apple Push Notification Network. Send this token to the server that you use to generate remote notifications.
|
||||
/// Your server must pass this token unmodified back to APNs when sending those remote notifications.
|
||||
/// This property will be empty if you set the registerForRemoteNotifications parameter to false when creating the Authorization request or if the app fails registration with the APN.
|
||||
/// </summary>
|
||||
public string DeviceToken
|
||||
{
|
||||
get { lock (this) { return m_DeviceToken; } }
|
||||
private set { m_DeviceToken = value; }
|
||||
}
|
||||
|
||||
static AuthorizationRequest()
|
||||
{
|
||||
iOSNotificationsWrapper.RegisterAuthorizationRequestCallback();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiate an authorization request.
|
||||
/// </summary>
|
||||
/// <param name="authorizationOption"> The authorization options your app is requesting. You may specify multiple options to request authorization for. Request only the authorization options that you plan to use.</param>
|
||||
/// <param name="registerForRemoteNotifications"> Set this to true to initiate the registration process with Apple Push Notification service after the user has granted authorization
|
||||
/// If registration succeeds the DeviceToken will be returned. You should pass this token along to the server you use to generate remote notifications for the device. </param>
|
||||
public AuthorizationRequest(AuthorizationOption authorizationOption, bool registerForRemoteNotifications)
|
||||
{
|
||||
var handle = GCHandle.Alloc(this);
|
||||
iOSNotificationsWrapper.RequestAuthorization(GCHandle.ToIntPtr(handle), (int)authorizationOption, registerForRemoteNotifications);
|
||||
}
|
||||
|
||||
private void OnAuthorizationRequestCompleted(iOSAuthorizationRequestData requestData)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
IsFinished = true;
|
||||
Granted = requestData.granted != 0;
|
||||
Error = requestData.error;
|
||||
DeviceToken = requestData.deviceToken;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void OnAuthorizationRequestCompleted(IntPtr request, iOSAuthorizationRequestData requestData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var handle = GCHandle.FromIntPtr(request);
|
||||
var req = handle.Target as AuthorizationRequest;
|
||||
handle.Free();
|
||||
req.OnAuthorizationRequestCompleted(requestData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the app is currently registered for remote notifications.
|
||||
/// </summary>
|
||||
/// <seealso cref="https://developer.apple.com/documentation/uikit/uiapplication/1623069-registeredforremotenotifications?language=objc"/>
|
||||
public static bool RegisteredForRemoteNotifications
|
||||
{
|
||||
get => iOSNotificationsWrapper.RegisteredForRemoteNotifications();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister for remote notifications.
|
||||
/// This is rarely needed, typically when user logs out. The app can always re-register.
|
||||
/// </summary>
|
||||
/// <seealso cref="https://developer.apple.com/documentation/uikit/uiapplication/1623093-unregisterforremotenotifications?language=objc"/>
|
||||
public static void UnregisterForRemoteNotifications()
|
||||
{
|
||||
iOSNotificationsWrapper.UnregisterForRemoteNotifications();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose to unregister the OnAuthorizationRequestCompleted callback.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b313ac86fae05a14cb6f9c95468075e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c6a1338bb05c4940b3099839651a262
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user