/**************************************************************************** * 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(); } } } } }