/****************************************************************************
* Copyright 2019 Nreal Techonology Limited. All rights reserved.
*
* This file is part of NRSDK.
*
* https://www.nreal.ai/
*
*****************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace NRKernal
{
/// A nr notification listener.
public class NRNotificationListener : MonoBehaviour
{
/// Values that represent levels.
public enum Level
{
All = 0,
Low,
Middle,
High,
Off
}
// Only the message which level is higher than notifLevel would be shown.
public Level notifLevel = Level.All;
/// Notification object base.
public class Notification
{
/// The notification listener.
protected NRNotificationListener NotificationListener;
/// Constructor.
/// The listener.
public Notification(NRNotificationListener listener)
{
this.NotificationListener = listener;
}
/// Updates the state.
public virtual void UpdateState() { }
/// Executes the 'state changed' action.
/// The level.
public virtual void Trigger(Level level)
{
NotificationListener.Dispath(this, level);
}
}
/// A low power notification.
public class LowPowerNotification : Notification
{
/// Values that represent power states.
public enum PowerState
{
/// An enum constant representing the full option.
Full,
/// An enum constant representing the middle option.
Middle,
/// An enum constant representing the low option.
Low
}
/// The current state.
private PowerState currentState = PowerState.Full;
/// Constructor.
/// The listener.
public LowPowerNotification(NRNotificationListener listener) : base(listener)
{
}
/// Gets state by value.
/// The value.
/// The state by value.
private PowerState GetStateByValue(float val)
{
if (val < 0.3f)
{
return PowerState.Low;
}
else if (val < 0.4f)
{
return PowerState.Middle;
}
return PowerState.Full;
}
/// Updates the state.
public override void UpdateState()
{
#if !UNITY_EDITOR
var state = GetStateByValue(SystemInfo.batteryLevel);
#else
var state = GetStateByValue(1f);
#endif
if (state != currentState)
{
if (state == PowerState.Low)
{
this.Trigger(Level.High);
}
else if (state == PowerState.Middle)
{
this.Trigger(Level.Middle);
}
this.currentState = state;
}
}
}
/// A slam state notification.
public class SlamStateNotification : Notification
{
/// Values that represent slam states.
private enum SlamState
{
/// An enum constant representing the none option.
None,
/// An enum constant representing the lost tracking option.
LostTracking,
/// An enum constant representing the tracking ready option.
TrackingReady
}
/// The current state.
private SlamState m_CurrentState = SlamState.None;
/// Constructor.
/// The listener.
public SlamStateNotification(NRNotificationListener listener) : base(listener)
{
NRHMDPoseTracker.OnHMDLostTracking += OnHMDLostTracking;
NRHMDPoseTracker.OnHMDPoseReady += OnHMDPoseReady;
}
/// Executes the 'hmd pose ready' action.
private void OnHMDPoseReady()
{
NRDebugger.Info("[SlamStateNotification] OnHMDPoseReady.");
m_CurrentState = SlamState.TrackingReady;
}
/// Executes the 'hmd lost tracking' action.
private void OnHMDLostTracking()
{
NRDebugger.Info("[SlamStateNotification] OnHMDLostTracking.");
if (m_CurrentState != SlamState.LostTracking)
{
this.Trigger(Level.Middle);
m_CurrentState = SlamState.LostTracking;
}
}
}
/// A temperature level notification.
public class TemperatureLevelNotification : Notification
{
/// The current state.
private GlassesTemperatureLevel currentState = GlassesTemperatureLevel.TEMPERATURE_LEVEL_NORMAL;
/// Constructor.
/// The listener.
public TemperatureLevelNotification(NRNotificationListener listener) : base(listener)
{
}
/// Updates the state.
public override void UpdateState()
{
base.UpdateState();
var level = NRDevice.Subsystem.TemperatureLevel;
if (currentState != level)
{
if (level != GlassesTemperatureLevel.TEMPERATURE_LEVEL_NORMAL)
{
this.Trigger(level == GlassesTemperatureLevel.TEMPERATURE_LEVEL_HOT
? Level.High : Level.Middle);
}
this.currentState = level;
}
}
}
/// Native interface error.
public class NativeErrorNotification : Notification
{
public NRKernalError KernalError { get; private set; }
/// Constructor.
/// The listener.
public NativeErrorNotification(NRNotificationListener listener) : base(listener)
{
NRSessionManager.OnKernalError += OnSessionError;
}
private void OnSessionError(NRKernalError exception)
{
KernalError = exception;
// Trigger the notification window
if (KernalError is NRRGBCameraDeviceNotFindError
|| KernalError is NRPermissionDenyError
|| KernalError is NRUnSupportedHandtrackingCalculationError)
{
this.Trigger(Level.High);
}
}
public string ErrorTitle
{
get
{
if (KernalError is NRRGBCameraDeviceNotFindError)
{
return "RGBCamera is disabled";
}
else if (KernalError is NRPermissionDenyError)
{
return "Permission Deny";
}
else if (KernalError is NRUnSupportedHandtrackingCalculationError)
{
return "Not support hand tracking calculation";
}
else
{
return KernalError.GetType().ToString();
}
}
}
public string ErrorContent
{
get
{
if (KernalError is NRRGBCameraDeviceNotFindError)
{
return NativeConstants.RGBCameraDeviceNotFindErrorTip;
}
else if (KernalError is NRUnSupportedHandtrackingCalculationError)
{
return NativeConstants.UnSupportedHandtrackingCalculationErrorTip;
}
else
{
return KernalError.GetErrorMsg();
}
}
}
}
/// True to enable, false to disable the low power tips.
[Header("Whether to open the low power prompt")]
public bool EnableLowPowerTips;
/// The low power notification prefab.
public NRNotificationWindow LowPowerNotificationPrefab;
/// True to enable, false to disable the slam state tips.
[Header("Whether to open the slam state prompt")]
public bool EnableSlamStateTips;
/// The slam state notification prefab.
public NRNotificationWindow SlamStateNotificationPrefab;
/// True to enable, false to disable the high temporary tips.
[Header("Whether to open the over temperature prompt")]
public bool EnableHighTempTips;
/// The high temporary notification prefab.
public NRNotificationWindow HighTempNotificationPrefab;
[Header("Whether to open the native interface error prompt")]
public bool EnableNativeNotifyTips;
public NRNotificationWindow NativeErrorNotificationPrefab;
/// List of notifications.
protected Dictionary NotificationDict = new Dictionary();
/// The tips last time.
private Dictionary TipsLastTime = new Dictionary() {
{ Level.High,3.5f},
{ Level.Middle,2.5f},
{ Level.Low,1.5f}
};
/// A notification message.
public struct NotificationMsg
{
/// The notification.
public Notification notification;
/// The level.
public Level level;
}
/// Queue of notifications.
private Queue NotificationQueue = new Queue();
private float m_LockTime = 0f;
private const float UpdateInterval = 1f;
private float m_TimeLast = 0f;
void Awake()
{
LowPowerNotificationPrefab.gameObject.SetActive(false);
SlamStateNotificationPrefab.gameObject.SetActive(false);
HighTempNotificationPrefab.gameObject.SetActive(false);
NativeErrorNotificationPrefab.gameObject.SetActive(false);
}
void Start()
{
DontDestroyOnLoad(gameObject);
RegistNotification();
}
/// Regist notification.
protected virtual void RegistNotification()
{
if (NRSessionManager.Instance.NRSessionBehaviour.SessionConfig.EnableNotification)
{
if (EnableLowPowerTips) BindNotificationWindow(new LowPowerNotification(this), LowPowerNotificationPrefab);
if (EnableSlamStateTips) BindNotificationWindow(new SlamStateNotification(this), SlamStateNotificationPrefab);
if (EnableHighTempTips) BindNotificationWindow(new TemperatureLevelNotification(this), HighTempNotificationPrefab);
}
if (EnableNativeNotifyTips) BindNotificationWindow(new NativeErrorNotification(this), NativeErrorNotificationPrefab);
}
public void BindNotificationWindow(Notification notification, NRNotificationWindow window)
{
if (NotificationDict.ContainsKey(notification))
{
NRDebugger.Error("[NRNotificationListener] Already has the notification.");
return;
}
NotificationDict.Add(notification, window);
}
///
/// Close all notification windows.
///
public void ClearAll()
{
notifLevel = Level.Off;
}
void Update()
{
// For Editor test
//if (Input.GetKeyDown(KeyCode.M))
//{
// var notifys = NotificationDict.Keys.ToArray();
// this.Dispath(notifys[UnityEngine.Random.Range(0, notifys.Length - 1)], Level.Middle);
//}
//if (Input.GetKeyDown(KeyCode.N))
//{
// var notifys = NotificationDict.Keys.ToArray();
// this.Dispath(notifys[UnityEngine.Random.Range(0, notifys.Length - 1)], Level.High);
//}
//if (Input.GetKeyDown(KeyCode.B))
//{
// var notifys = NotificationDict.Keys.ToArray();
// this.Dispath(notifys[3], Level.High);
//}
m_TimeLast += Time.deltaTime;
if (m_TimeLast < UpdateInterval)
{
return;
}
m_TimeLast = 0;
foreach (var item in NotificationDict)
{
item.Key.UpdateState();
}
if (m_LockTime < float.Epsilon)
{
if (NotificationQueue.Count != 0)
{
var msg = NotificationQueue.Dequeue();
this.OprateNotificationMsg(msg);
m_LockTime = TipsLastTime[msg.level];
}
}
else
{
m_LockTime -= UpdateInterval;
}
}
/// Dispaths notification message.
/// The notification.
/// The level.
public void Dispath(Notification notification, Level lev)
{
if (lev >= notifLevel)
{
NotificationQueue.Enqueue(new NotificationMsg()
{
notification = notification,
level = lev
});
}
}
/// Oprate notification message.
/// The message.
protected virtual void OprateNotificationMsg(NotificationMsg msg)
{
NRNotificationWindow prefab = NotificationDict[msg.notification];
Notification notification_obj = msg.notification;
Level notification_level = msg.level;
float duration = TipsLastTime[notification_level];
Action onConfirm = null;
// Notification window will not be destroyed automatic when lowpower and high level warning
// Set it's duration to -1
if (notification_obj is LowPowerNotification)
{
if (notification_level == Level.High)
{
duration = -1f;
onConfirm = () =>
{
NRDevice.QuitApp();
};
}
}
if (prefab != null)
{
NRDebugger.Info("[NRNotificationListener] Dispath:" + notification_obj.GetType().ToString());
NRNotificationWindow notification = Instantiate(prefab);
notification.gameObject.SetActive(true);
notification.transform.SetParent(transform);
if (notification_obj is NativeErrorNotification)
{
string title = ((NativeErrorNotification)notification_obj).ErrorTitle;
string content = ((NativeErrorNotification)notification_obj).ErrorContent;
notification.SetLevle(notification_level)
.SetDuration(duration)
.SetTitle(title)
.SetContent(content)
.SetConfirmAction(onConfirm)
.Build();
}
else
{
notification.SetLevle(notification_level)
.SetDuration(duration)
.SetConfirmAction(onConfirm)
.Build();
}
}
}
}
}