[NUI] Fix to disable ThemeManager in tv profile
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Theme / ThemeManager.cs
index 6ef43b6..d7e85d1 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright(c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright(c) 2021 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  *
  */
+
+#if !PROFILE_TV
+#define ExternalThemeEnabled
+#endif
+
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
-using Tizen.NUI;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using Tizen.NUI.BaseComponents;
 
 namespace Tizen.NUI
 {
-    internal interface IThemeCreator
-    {
-        Theme Create();
-    }
-
     /// <summary>
-    /// This static module provides methods that can manage NUI <seealso cref="Theme"/>.
+    /// This static module provides methods that can manage NUI <see cref="Theme"/>.
     /// </summary>
     /// <example>
-    /// To apply custom theme to the application, try <seealso cref="ApplyTheme(Theme)"/>.
+    /// To apply custom theme to the application, try <see cref="ApplyTheme(Theme)"/>.
     /// <code>
     /// var customTheme = new Theme(res + "customThemeFile.xaml");
     /// ThemeManager.ApplyTheme(customTheme);
     /// </code>
     /// </example>
     /// <summary></summary>
-    [EditorBrowsable(EditorBrowsableState.Never)]
+    /// <since_tizen> 9 </since_tizen>
     public static class ThemeManager
     {
-        private static readonly string[] nuiThemeProjects =
-        {
-            "Tizen.NUI",
-            "Tizen.NUI.Components",
-            "Tizen.NUI.Wearable"
-        };
-
-        private static Theme currentTheme;
-        private static Theme defaultTheme;
-        private static readonly List<Theme> builtinThemes = new List<Theme>(); // Themes provided by framework.
-        private static readonly List<Theme> customThemes = new List<Theme>(); // Themes registered by user. (Legacy support)
-        private static readonly List<string> packages = new List<string>();
+        private static Theme baseTheme; // The base theme. It includes all styles including structures (Size, Position, Policy) of components.
+        private static Theme platformTheme; // The platform theme. This may include color and image information without structure detail.
+        private static Theme userTheme; // The user custom theme.
+        private static Theme themeForUpdate; // platformTheme + userTheme. It is used when the component need to update according to theme change.
+        private static Theme themeForInitialize; // baseTheme + platformTheme + userTheme. It is used when the component is created.
+        private static readonly List<Theme> cachedPlatformThemes = new List<Theme>(); // Themes provided by framework.
+        private static readonly List<IThemeCreator> packages = new List<IThemeCreator>();// This is to store base theme creators by packages.
+        private static bool platformThemeEnabled = false;
 
         static ThemeManager()
         {
-            defaultTheme = new DefaultThemeCreator().Create();
-            builtinThemes.Add(defaultTheme);
+#if ExternalThemeEnabled
+            ExternalThemeManager.Initialize();
+#endif
+            AddPackageTheme(DefaultThemeCreator.Instance);
         }
 
         /// <summary>
-        /// An event invoked after the theme has changed by <seealso cref="ApplyTheme(Theme)"/>.
+        /// An event invoked after the theme has changed by <see cref="ApplyTheme(Theme)"/>.
         /// </summary>
-        [EditorBrowsable(EditorBrowsableState.Never)]
+        /// <since_tizen> 9 </since_tizen>
         public static event EventHandler<ThemeChangedEventArgs> ThemeChanged;
 
         /// <summary>
@@ -71,47 +70,90 @@ namespace Tizen.NUI
         /// </summary>
         internal static WeakEvent<EventHandler<ThemeChangedEventArgs>> ThemeChangedInternal = new WeakEvent<EventHandler<ThemeChangedEventArgs>>();
 
-        internal static Theme CurrentTheme
+        /// <summary>
+        /// The current theme Id.
+        /// It returns null when no theme is applied.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static string ThemeId
         {
-            get => currentTheme;
-            set
-            {
-                currentTheme = value;
-                NotifyThemeChanged();
-            }
+            get => userTheme?.Id;
         }
 
         /// <summary>
-        /// The fallback theme that depends on the device profile.
+        /// The current platform theme Id.
+        /// Note that it returns null when the platform theme is disabled.
+        /// If the <seealso cref="NUIApplication.ThemeOptions.PlatformThemeEnabled"/> is given, it can be one of followings in tizen 6.5:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>org.tizen.default-light-theme</description>
+        /// </item>
+        /// <item>
+        /// <description>org.tizen.default-dark-theme</description>
+        /// </item>
+        /// </list>
         /// </summary>
-        internal static Theme DefaultTheme
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static string PlatformThemeId
         {
-            get => defaultTheme;
-            set => defaultTheme = (Theme)value?.Clone();
+            get => platformTheme?.Id ?? (platformThemeEnabled ? baseTheme.Id : null);
         }
 
-        internal static bool ThemeApplied => DefaultTheme.Count > 0 || (currentTheme != null && currentTheme.Count > 0);
+        /// <summary>
+        /// To support deprecated StyleManager.
+        /// NOTE that, please remove this after remove Tizen.NUI.Components.StyleManager
+        /// </summary>
+        internal static Theme BaseTheme
+        {
+            get => baseTheme;
+            set
+            {
+                baseTheme = value;
+                UpdateThemeForInitialize();
+            }
+        }
 
         /// <summary>
-        /// Set a theme to be used as fallback.
-        /// The fallback theme is set to profile specified theme by default.
+        /// To support deprecated StyleManager.
+        /// NOTE that, please remove this after remove Tizen.NUI.Components.StyleManager
         /// </summary>
-        /// <param name="fallbackTheme">The theme instance to be applied as a fallback.</param>
-        /// <exception cref="ArgumentNullException">The given theme is null.</exception>
-        [EditorBrowsable(EditorBrowsableState.Never)]
-        public static void ApplyFallbackTheme(Theme fallbackTheme)
+        internal static Theme CurrentTheme
         {
-            DefaultTheme = fallbackTheme ?? throw new ArgumentNullException(nameof(fallbackTheme));
+            get => userTheme ?? baseTheme;
+            set
+            {
+                userTheme = value;
+                UpdateThemeForInitialize();
+                NotifyThemeChanged();
+            }
         }
 
+        internal static bool PlatformThemeEnabled
+        {
+            get => platformThemeEnabled;
+            set
+            {
+                if (platformThemeEnabled == value) return;
+
+                platformThemeEnabled = value;
+
+                if (platformThemeEnabled)
+                {
+                    ApplyExternalPlatformTheme(ExternalThemeManager.CurrentThemeId, ExternalThemeManager.CurrentThemeVersion);
+                }
+            }
+        }
+
+        internal static bool ApplicationThemeChangeSensitive { get; set; } = false;
+
         /// <summary>
-        /// Apply theme to the NUI.
-        /// This will change the appreance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
+        /// Apply custom theme to the NUI.
+        /// This will change the appearance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
         /// This also affects all components created afterwards.
         /// </summary>
         /// <param name="theme">The theme instance to be applied.</param>
         /// <exception cref="ArgumentNullException">Thrown when the given theme is null.</exception>
-        [EditorBrowsable(EditorBrowsableState.Never)]
+        /// <since_tizen> 9 </since_tizen>
         public static void ApplyTheme(Theme theme)
         {
             var newTheme = (Theme)theme?.Clone() ?? throw new ArgumentNullException(nameof(theme));
@@ -121,156 +163,372 @@ namespace Tizen.NUI
                 newTheme.Id = "NONAME";
             }
 
-            CurrentTheme = newTheme;
+            StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Small, newTheme.SmallBrokenImageUrl ?? "");
+            StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Normal, newTheme.BrokenImageUrl ?? "");
+            StyleManager.Instance.SetBrokenImageUrl(StyleManager.BrokenImageType.Large, newTheme.LargeBrokenImageUrl ?? "");
+
+            userTheme = newTheme;
+            UpdateThemeForInitialize();
+            UpdateThemeForUpdate();
+            NotifyThemeChanged();
         }
 
         /// <summary>
-        /// <para>
-        /// Note that this API is to support legacy Tizen.NUI.Components.StyleManager.
-        /// Please use <seealso cref="ApplyTheme(Theme)"/> instead.
-        /// </para>
-        /// <para>
-        /// Apply theme to the NUI using theme id.
-        /// The id of theme should be either a registered custom theme or a built-in theme.
-        /// You can register custom theme using <seealso cref="RegisterTheme(Theme)"/>.
-        /// This will change the appreance of the existing components with property <seealso cref="View.ThemeChangeSensitive"/> on.
-        /// This also affects all components created afterwards.
-        /// </para>
+        /// Change tizen theme.
+        /// User may change this to one of platform installed one.
         /// </summary>
-        /// <param name="themeId">The theme Id.</param>
+        /// <param name="themeId">The installed theme Id.</param>
+        /// <returns>true on success, false when it failed to find installed theme with given themeId.</returns>
         /// <exception cref="ArgumentNullException">Thrown when the given themeId is null.</exception>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public static void ApplyTheme(string themeId)
+        public static bool ApplyPlatformTheme(string themeId)
         {
             if (themeId == null) throw new ArgumentNullException(nameof(themeId));
 
-            int index = customThemes.FindIndex(x => x.Id.Equals(themeId, StringComparison.OrdinalIgnoreCase));
-            if (index >= 0)
-            {
-                CurrentTheme = customThemes[index];
-                return;
-            }
-
-            index = builtinThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase));
-            if (index >= 0)
-            {
-                CurrentTheme = builtinThemes[index];
-            }
-            else
-            {
-                Tizen.Log.Info("NUI", $"No Theme found with given id : {themeId}");
-            }
+            return ExternalThemeManager.SetTheme(themeId);
         }
 
         /// <summary>
-        /// <para> Note that this API is to support legacy Tizen.NUI.Components.StyleManager. </para>
-        /// <para> Register a custom theme that can be used as an id when calling <seealso cref="ApplyTheme(string)"/>. </para>
+        /// Load a style with style name in the current theme.
+        /// For components, the default style name of a component is a component name with namespace (e.g. Tizen.NUI.Components.Button).
         /// </summary>
-        /// <param name="theme">The theme instance.</param>
-        /// <exception cref="ArgumentNullException">Thrown when the given theme is null.</exception>
-        /// <exception cref="ArgumentException">Thrown when the given theme id is invalid.</exception>
-        [EditorBrowsable(EditorBrowsableState.Never)]
-        public static void RegisterTheme(Theme theme)
+        /// <param name="styleName">The style name.</param>
+        /// <exception cref="ArgumentNullException">Thrown when the given styleName is null.</exception>
+        /// <since_tizen> 9 </since_tizen>
+        public static ViewStyle GetStyle(string styleName)
         {
-            if (theme == null) throw new ArgumentNullException(nameof(theme));
-            if (string.IsNullOrEmpty(theme.Id)) throw new ArgumentException("Invalid theme id.");
+            if (styleName == null) throw new ArgumentNullException(nameof(styleName));
+            return GetInitialStyleWithoutClone(styleName)?.Clone();
+        }
 
-            int index = customThemes.FindIndex(x => x.Id.Equals(theme.Id, StringComparison.OrdinalIgnoreCase));
-            if (index >= 0)
-            {
-                customThemes[index] = (Theme)theme.Clone();
-            }
-            else
-            {
-                customThemes.Add((Theme)theme.Clone());
-            }
+        /// <summary>
+        /// Load a style with view type in the current theme.
+        /// If it failed to find a style with the given type, it will try with it's parent type until it succeeds.
+        /// </summary>
+        /// <param name="viewType"> The type of the view. Full name of the given type will be a key to find a style in the current theme. (e.g. Tizen.NUI.Components.Button) </param>
+        /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
+        /// <since_tizen> 9 </since_tizen>
+        public static ViewStyle GetStyle(Type viewType)
+        {
+            if (viewType == null) throw new ArgumentNullException(nameof(viewType));
+            return GetInitialStyleWithoutClone(viewType)?.Clone();
         }
 
         /// <summary>
-        /// Load a style with style name in the current theme.
-        /// For components, the style name is a component name (e.g. Button) in normal case.
+        /// Load a platform style with style name in the current theme.
+        /// It returns null when the platform theme is disabled. <see cref="NUIApplication.ThemeOptions.PlatformThemeEnabled" />.
         /// </summary>
         /// <param name="styleName">The style name.</param>
         /// <exception cref="ArgumentNullException">Thrown when the given styleName is null.</exception>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public static ViewStyle GetStyle(string styleName)
+        public static ViewStyle GetPlatformStyle(string styleName)
         {
             if (styleName == null) throw new ArgumentNullException(nameof(styleName));
-
-            if (!ThemeApplied) return null;
-
-            return (CurrentTheme?.GetStyle(styleName) ?? DefaultTheme.GetStyle(styleName))?.Clone();
+            return platformTheme?.GetStyle(styleName)?.Clone();
         }
 
         /// <summary>
-        /// Load a style with View type in the current theme.
+        /// Load a platform style with view type in the current theme.
+        /// It returns null when the platform theme is disabled. <see cref="NUIApplication.ThemeOptions.PlatformThemeEnabled" />.
         /// </summary>
-        /// <param name="viewType">The type of View.</param>
+        /// <param name="viewType"> The type of the view. Full name of the given type will be a key to find a style in the current theme. (e.g. Tizen.NUI.Components.Button) </param>
         /// <exception cref="ArgumentNullException">Thrown when the given viewType is null.</exception>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public static ViewStyle GetStyle(Type viewType)
+        public static ViewStyle GetPlatformStyle(Type viewType)
         {
             if (viewType == null) throw new ArgumentNullException(nameof(viewType));
+            return platformTheme?.GetStyle(viewType)?.Clone();
+        }
+
+        /// <summary>
+        /// Load a style with style name in the current theme.
+        /// </summary>
+        /// <param name="styleName">The style name.</param>
+        internal static ViewStyle GetUpdateStyleWithoutClone(string styleName) => themeForUpdate?.GetStyle(styleName);
+
+        /// <summary>
+        /// Load a style with View type in the current theme.
+        /// </summary>
+        /// <param name="viewType">The type of View.</param>
+        internal static ViewStyle GetUpdateStyleWithoutClone(Type viewType) => themeForUpdate?.GetStyle(viewType);
+
+        /// <summary>
+        /// Load a initial component style.
+        /// </summary>
+        internal static ViewStyle GetInitialStyleWithoutClone(string styleName) => themeForInitialize.GetStyle(styleName);
+
+        /// <summary>
+        /// Load a initial component style.
+        /// </summary>
+        internal static ViewStyle GetInitialStyleWithoutClone(Type viewType) => themeForInitialize.GetStyle(viewType);
+
+        /// <summary>
+        /// Get a platform installed theme.
+        /// </summary>
+        /// <param name="themeId">The theme id.</param>
+        internal static Theme LoadPlatformTheme(string themeId)
+        {
+            Debug.Assert(themeId != null);
 
-            if (!ThemeApplied) return null;
+            // Check if it is already loaded.
+            int index = cachedPlatformThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase));
+            if (index >= 0)
+            {
+                Tizen.Log.Info("NUI", $"Hit cache.");
+                var found = cachedPlatformThemes[index];
+                // If the cached is not a full set, update it.
+                if (found.PackageCount < packages.Count)
+                {
+                    UpdatePlatformTheme(found);
+                    Tizen.Log.Info("NUI", $"Update cache.");
+                }
+                return found;
+            }
 
-            return (CurrentTheme?.GetStyle(viewType) ?? DefaultTheme.GetStyle(viewType))?.Clone();
+            var newTheme = CreatePlatformTheme(themeId);
+            if (newTheme != null)
+            {
+                cachedPlatformThemes.Add(newTheme);
+                Tizen.Log.Info("NUI", $"Platform theme has been loaded successfully.");
+            }
+            return newTheme;
         }
 
         /// <summary>
-        /// Get a cloned built-in theme.
+        /// !!! This is for internal use in fhub-nui. Please do not open it.
+        /// Set a theme to be used as fallback.
+        /// The fallback theme is set to profile specified theme by default.
         /// </summary>
-        /// <param name="themeId">The built-in theme id.</param>
-        /// <exception cref="ArgumentNullException">Thrown when the given themeId is null.</exception>
+        /// <param name="fallbackTheme">The theme instance to be applied as a fallback.</param>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public static Theme GetBuiltinTheme(string themeId)
+        internal static void ApplyFallbackTheme(Theme fallbackTheme)
         {
-            if (themeId == null) throw new ArgumentNullException(nameof(themeId));
+            Debug.Assert(fallbackTheme != null);
+            BaseTheme = (Theme)fallbackTheme?.Clone();
+        }
 
-            Theme result = null;
-            int index = builtinThemes.FindIndex(x => string.Equals(x.Id, themeId, StringComparison.OrdinalIgnoreCase));
-            if (index >= 0)
+        /// <summary>
+        /// Apply an external platform theme.
+        /// </summary>
+        /// <param name="id">The external theme id.</param>
+        /// <param name="version">The external theme version.</param>
+        internal static void ApplyExternalPlatformTheme(string id, string version)
+        {
+#if ExternalThemeEnabled
+            Debug.Assert(baseTheme != null);
+
+            // If the given theme is invalid, do nothing.
+            if (string.IsNullOrEmpty(id))
             {
-                result = builtinThemes[index];
+                return;
             }
-            else
+
+            // If no platform theme has been applied and the base theme can cover the given one, do nothing.
+            if (platformTheme == null && baseTheme.HasSameIdAndVersion(id, version))
             {
-                var theme = LoadBuiltinTheme(themeId);
-                builtinThemes.Add(theme);
-                result = theme;
+                Tizen.Log.Info("NUI", "The base theme can cover platform theme: Skip loading.");
+                return;
             }
-            return (Theme)result?.Clone();
+
+            // If the given theme is already applied, do nothing.
+            if (platformTheme != null && platformTheme.HasSameIdAndVersion(id, version))
+            {
+                Tizen.Log.Info("NUI", "Platform theme is already applied: Skip loading.");
+                return;
+            }
+
+            var loaded = LoadPlatformTheme(id);
+
+            if (loaded != null)
+            {
+                Tizen.Log.Info("NUI", $"{loaded.Id} has been applied successfully.");
+                platformTheme = loaded;
+                UpdateThemeForInitialize();
+                UpdateThemeForUpdate();
+                NotifyThemeChanged(true);
+            }
+#endif
         }
 
         internal static void AddPackageTheme(IThemeCreator themeCreator)
         {
-            string name = themeCreator.GetType().FullName;
-            if (packages.FindIndex(x => string.Equals(x, name)) >= 0)
+            if (packages.Contains(themeCreator))
             {
                 return;
             }
-            packages.Add(name);
-            DefaultTheme.MergeWithoutClone(themeCreator.Create());
+
+            Tizen.Log.Info("NUI", $"AddPackageTheme({themeCreator.GetType().Assembly.GetName().Name})");
+            packages.Add(themeCreator);
+
+            // Base theme
+            var packageBaseTheme = themeCreator.Create();
+            Debug.Assert(packageBaseTheme != null);
+
+            if (baseTheme == null) baseTheme = packageBaseTheme;
+            else baseTheme.MergeWithoutClone(packageBaseTheme);
+            baseTheme.PackageCount++;
+
+#if ExternalThemeEnabled
+            if (platformThemeEnabled)
+            {
+                Tizen.Log.Info("NUI", $"Platform theme is enabled");
+                if (platformTheme != null)
+                {
+                    UpdatePlatformTheme(platformTheme);
+                }
+                else
+                {
+                    if (!string.IsNullOrEmpty(ExternalThemeManager.CurrentThemeId) && !baseTheme.HasSameIdAndVersion(ExternalThemeManager.CurrentThemeId, ExternalThemeManager.CurrentThemeVersion))
+                    {
+                        var loaded = LoadPlatformTheme(ExternalThemeManager.CurrentThemeId);
+                        if (loaded != null)
+                        {
+                            platformTheme = loaded;
+                        }
+                    }
+                }
+                UpdateThemeForUpdate();
+            }
+#endif
+            UpdateThemeForInitialize();
         }
 
-        private static Theme LoadBuiltinTheme(string id)
+        internal static void Preload()
         {
-            var loaded = new Theme()
+#if ExternalThemeEnabled
+            Debug.Assert(baseTheme != null);
+
+            if (string.IsNullOrEmpty(ExternalThemeManager.CurrentThemeId)) return;
+
+            LoadPlatformTheme(ExternalThemeManager.CurrentThemeId);
+#endif
+        }
+
+        // TODO Please make it private after removing Tizen.NUI.Components.StyleManager.
+        internal static void UpdateThemeForUpdate()
+        {
+            if (userTheme == null)
+            {
+                themeForUpdate = platformTheme;
+                return;
+            }
+
+            if (platformTheme == null)
             {
-                Id = id,
+                themeForUpdate = userTheme;
+                return;
+            }
+
+            themeForUpdate = new Theme();
+            themeForUpdate.Merge(platformTheme);
+            themeForUpdate.MergeWithoutClone(userTheme);
+        }
+
+        // TODO Please make it private after removing Tizen.NUI.Components.StyleManager.
+        internal static void UpdateThemeForInitialize()
+        {
+            if (platformTheme == null && userTheme == null)
+            {
+                themeForInitialize = baseTheme;
+                return;
+            }
+
+            themeForInitialize = new Theme();
+            themeForInitialize.Merge(baseTheme);
+
+            if (userTheme == null)
+            {
+                if (platformTheme != null) themeForInitialize.MergeWithoutClone(platformTheme);
+            }
+            else
+            {
+                if (platformTheme != null) themeForInitialize.Merge(platformTheme);
+                themeForInitialize.MergeWithoutClone(userTheme);
+            }
+        }
+
+        private static void UpdatePlatformTheme(Theme theme)
+        {
+            var sharedResourcePath = ExternalThemeManager.GetSharedResourcePath(theme.Id);
+
+            if (sharedResourcePath == null)
+            {
+                return;
+            }
+
+            for (var i = theme.PackageCount; i < packages.Count; i++)
+            {
+                theme.MergeWithoutClone(CreatePlatformTheme(sharedResourcePath, packages[i].GetType().Assembly.GetName().Name));
+            }
+            theme.PackageCount = packages.Count;
+        }
+
+        private static Theme CreatePlatformTheme(string id)
+        {
+            var sharedResourcePath = ExternalThemeManager.GetSharedResourcePath(id);
+
+            if (sharedResourcePath == null)
+            {
+                return null;
+            }
+
+            var newTheme = new Theme()
+            {
+                Id = id
             };
 
-            if (string.IsNullOrEmpty(id)) return loaded;
+            foreach (var packageCreator in packages)
+            {
+                newTheme.MergeWithoutClone(CreatePlatformTheme(sharedResourcePath, packageCreator.GetType().Assembly.GetName().Name));
+            }
+            newTheme.PackageCount = packages.Count;
 
-            // TODO
+            return newTheme;
+        }
 
-            return loaded;
+        [SuppressMessage("Microsoft.Design", "CA1031: Do not catch general exception types", Justification = "This method is to handle external resources that may throw an exception but ignorable. This method should not interrupt the main stream.")]
+        private static Theme CreatePlatformTheme(string sharedResourcePath, string assemblyName)
+        {
+            ExternalThemeManager.SharedResourcePath = sharedResourcePath;
+            try
+            {
+                return new Theme(sharedResourcePath + assemblyName + ".Theme.xaml");
+            }
+            catch (System.IO.FileNotFoundException)
+            {
+                Tizen.Log.Info("NUI", $"[Ignorable] Current tizen theme does not have NUI theme.");
+            }
+            catch (Exception e)
+            {
+                Tizen.Log.Info("NUI", $"[Ignorable] {e.GetType().Name} occurred while applying tizen theme to {assemblyName}: {e.Message}");
+            }
+
+            return new Theme();
+        }
+
+        private static void AddToPlatformThemes(Theme theme)
+        {
+            int index = cachedPlatformThemes.FindIndex(x => x.Id.Equals(theme.Id, StringComparison.OrdinalIgnoreCase));
+            if (index >= 0)
+            {
+                Tizen.Log.Info("NUI", $"Existing {theme.Id} item is overwritten");
+                cachedPlatformThemes[index] = theme;
+            }
+            else
+            {
+                cachedPlatformThemes.Add(theme);
+                Tizen.Log.Info("NUI", $"New {theme.Id} is saved.");
+            }
         }
 
-        private static void NotifyThemeChanged()
+        private static void NotifyThemeChanged(bool platformThemeUpdated = false)
         {
-            ThemeChangedInternal.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id));
-            ThemeChanged?.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id));
+            Debug.Assert(baseTheme != null);
+
+            var platformThemeId = PlatformThemeId;
+            var userThemeId = userTheme?.Id;
+            ThemeChangedInternal.Invoke(null, new ThemeChangedEventArgs(userThemeId, platformThemeId, platformThemeUpdated));
+            ThemeChanged?.Invoke(null, new ThemeChangedEventArgs(userThemeId, platformThemeId, platformThemeUpdated));
         }
     }
 }