/*
* Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using Tizen.Applications.CoreBackend;
namespace Tizen.Applications
{
///
/// This class represents an application controlled lifecycles by the backend system.
///
/// 3
public class CoreApplication : Application
{
private readonly ICoreBackend _backend;
private readonly ICoreTask _task;
private bool _disposedValue = false;
///
/// Initializes the CoreApplication class.
///
/// The backend instance implementing ICoreBacked interface.
/// 3
public CoreApplication(ICoreBackend backend)
{
_backend = backend;
_task = null;
}
///
/// Initializes the CoreApplication class.
///
/// The backend instance implementing ICoreBackend interface.
/// The backend instance implmenting ICoreTask interface.
/// 10
[EditorBrowsable(EditorBrowsableState.Never)]
public CoreApplication(ICoreBackend backend, ICoreTask task)
{
_backend = backend;
_task = task;
}
///
/// Occurs when the application is launched.
///
/// 3
public event EventHandler Created;
///
/// Occurs when the application is about to shutdown.
///
/// 3
public event EventHandler Terminated;
///
/// Occurs whenever the application receives the appcontrol message.
///
/// 3
public event EventHandler AppControlReceived;
///
/// Occurs when the system memory is low.
///
/// 3
public event EventHandler LowMemory;
///
/// Occurs when the system battery is low.
///
/// 3
public event EventHandler LowBattery;
///
/// Occurs when the system language is chagned.
///
/// 3
public event EventHandler LocaleChanged;
///
/// Occurs when the region format is changed.
///
/// 3
public event EventHandler RegionFormatChanged;
///
/// Occurs when the device orientation is changed.
///
/// 3
public event EventHandler DeviceOrientationChanged;
///
/// Occurs when the time zone is changed.
///
/// 11
public event EventHandler TimeZoneChanged;
///
/// The backend instance.
///
/// 3
protected ICoreBackend Backend { get { return _backend; } }
///
/// Runs the application's main loop.
///
/// Arguments from commandline.
/// 3
public override void Run(string[] args)
{
base.Run(args);
_backend.AddEventHandler(EventType.Created, OnCreate);
_backend.AddEventHandler(EventType.Terminated, OnTerminate);
_backend.AddEventHandler(EventType.AppControlReceived, OnAppControlReceived);
_backend.AddEventHandler(EventType.LowMemory, OnLowMemory);
_backend.AddEventHandler(EventType.LowBattery, OnLowBattery);
_backend.AddEventHandler(EventType.LocaleChanged, OnLocaleChanged);
_backend.AddEventHandler(EventType.RegionFormatChanged, OnRegionFormatChanged);
_backend.AddEventHandler(EventType.DeviceOrientationChanged, OnDeviceOrientationChanged);
_backend.AddEventHandler(EventType.TimeZoneChanged, OnTimeZoneChanged);
string[] argsClone = new string[args == null ? 1 : args.Length + 1];
if (args != null && args.Length > 1)
{
args.CopyTo(argsClone, 1);
}
argsClone[0] = string.Empty;
if (_task != null)
{
ICoreTaskBackend backend = (ICoreTaskBackend)_backend;
backend.SetCoreTask(_task);
backend.Run(argsClone);
}
else
{
_backend.Run(argsClone);
}
}
///
/// Exits the main loop of the application.
///
/// 3
public override void Exit()
{
_backend.Exit();
}
///
/// Overrides this method if want to handle behavior when the application is launched.
/// If base.OnCreated() is not called, the event 'Created' will not be emitted.
///
/// 3
protected virtual void OnCreate()
{
if (_task != null)
{
TizenUISynchronizationContext.Initialize();
}
if (!GlobalizationMode.Invariant)
{
string locale = ULocale.GetDefaultLocale();
ChangeCurrentUICultureInfo(locale);
ChangeCurrentCultureInfo(locale);
}
else
{
Log.Warn(LogTag, "Run in invariant mode");
}
Created?.Invoke(this, EventArgs.Empty);
}
///
/// Overrides this method if want to handle behavior when the application is terminated.
/// If base.OnTerminate() is not called, the event 'Terminated' will not be emitted.
///
/// 3
protected virtual void OnTerminate()
{
Terminated?.Invoke(this, EventArgs.Empty);
}
///
/// Overrides this method if want to handle behavior when the application receives the appcontrol message.
/// If base.OnAppControlReceived() is not called, the event 'AppControlReceived' will not be emitted.
///
///
/// 3
protected virtual void OnAppControlReceived(AppControlReceivedEventArgs e)
{
if (e == null)
{
Log.Error(LogTag, "e is null");
return;
}
AppControlReceived?.Invoke(this, e);
}
///
/// Overrides this method if want to handle behavior when the system memory is low.
/// If base.OnLowMemory() is not called, the event 'LowMemory' will not be emitted.
///
/// The low memory event argument
/// 3
protected virtual void OnLowMemory(LowMemoryEventArgs e)
{
if (e == null)
{
Log.Error(LogTag, "e is null");
return;
}
LowMemory?.Invoke(this, e);
if (e.LowMemoryStatus == LowMemoryStatus.SoftWarning || e.LowMemoryStatus == LowMemoryStatus.HardWarning)
{
System.GC.Collect();
}
}
///
/// Overrides this method if want to handle behavior when the system battery is low.
/// If base.OnLowBattery() is not called, the event 'LowBattery' will not be emitted.
///
/// The low battery event argument
/// 3
protected virtual void OnLowBattery(LowBatteryEventArgs e)
{
if (e == null)
{
Log.Error(LogTag, "e is null");
return;
}
LowBattery?.Invoke(this, e);
}
///
/// Overrides this method if want to handle behavior when the system language is changed.
/// If base.OnLocaleChanged() is not called, the event 'LocaleChanged' will not be emitted.
///
/// The locale changed event argument
/// 3
protected virtual void OnLocaleChanged(LocaleChangedEventArgs e)
{
if (e == null)
{
Log.Error(LogTag, "e is null");
return;
}
if (!GlobalizationMode.Invariant)
{
ChangeCurrentUICultureInfo(e.Locale);
}
LocaleChanged?.Invoke(this, e);
}
///
/// Overrides this method if want to handle behavior when the region format is changed.
/// If base.OnRegionFormatChanged() is not called, the event 'RegionFormatChanged' will not be emitted.
///
/// The region format changed event argument
/// 3
protected virtual void OnRegionFormatChanged(RegionFormatChangedEventArgs e)
{
if (e == null)
{
Log.Error(LogTag, "e is null");
return;
}
if (!GlobalizationMode.Invariant)
{
ChangeCurrentCultureInfo(e.Region);
}
RegionFormatChanged?.Invoke(this, e);
}
///
/// Overrides this method if want to handle behavior when the device orientation is changed.
/// If base.OnRegionFormatChanged() is not called, the event 'RegionFormatChanged' will not be emitted.
///
/// The device orientation changed event argument
/// 3
protected virtual void OnDeviceOrientationChanged(DeviceOrientationEventArgs e)
{
DeviceOrientationChanged?.Invoke(this, e);
}
///
/// Override this method if you want to handle behavior when the time zone is changed.
/// If base.OnTimeZoneChanged() is not called, the event "TimeZoneChanged" will not be emitted.
///
/// The time zone changed event argument
/// 11
protected virtual void OnTimeZoneChanged(TimeZoneChangedEventArgs e)
{
TimeZoneChanged?.Invoke(this, e);
}
///
/// Dispatches an asynchronous message to a main loop of the CoreApplication.
///
///
/// If an application uses UI thread App Model, the asynchronous message will be delivered to the UI thread.
/// If not, the asynchronous message will be delivered to the main thread.
///
/// The runner callaback.
/// Thrown when the runner is null.
/// 10
[EditorBrowsable(EditorBrowsableState.Never)]
public static void Post(Action runner)
{
if (runner == null)
{
throw new ArgumentNullException(nameof(runner));
}
GSourceManager.Post(runner, true);
}
///
/// Dispatches an asynchronous message to a main loop of the CoreApplication.
///
///
/// If an application uses UI thread App Model, the asynchronous message will be delivered to the UI thread.
/// If not, the asynchronous message will be delivered to the main thread.
///
/// The type of the result.
/// The runner callback.
/// Thrown when the runner is null.
/// A task with the result.
/// 10
[EditorBrowsable(EditorBrowsableState.Never)]
public static async Task Post(Func runner)
{
if (runner == null)
{
throw new ArgumentNullException(nameof(runner));
}
var task = new TaskCompletionSource();
GSourceManager.Post(() => { task.SetResult(runner()); }, true);
return await task.Task.ConfigureAwait(false);
}
///
/// Releases any unmanaged resources used by this object. Can also dispose any other disposable objects.
///
/// If true, disposes any disposable objects. If false, does not dispose disposable objects.
/// 3
protected override void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_backend.Dispose();
}
_disposedValue = true;
}
base.Dispose(disposing);
}
private CultureInfo ConvertCultureInfo(string locale)
{
ULocale pLocale = new ULocale(locale);
string cultureName = CultureInfoHelper.GetCultureName(pLocale.Locale.Replace("_", "-"));
if (!string.IsNullOrEmpty(cultureName))
{
try
{
return new CultureInfo(cultureName);
}
catch (CultureNotFoundException)
{
Log.Error(LogTag, "CultureNotFoundException occurs. CultureName: " + cultureName);
}
}
try
{
return new CultureInfo(pLocale.LCID);
}
catch (ArgumentOutOfRangeException)
{
return GetFallbackCultureInfo(pLocale);
}
catch (CultureNotFoundException)
{
return GetFallbackCultureInfo(pLocale);
}
}
private void ChangeCurrentCultureInfo(string locale)
{
CultureInfo cultureInfo = ConvertCultureInfo(locale);
if (cultureInfo != null)
{
CultureInfo.CurrentCulture = cultureInfo;
}
else
{
Log.Error(LogTag, "CultureInfo is null. locale: " + locale);
}
}
private void ChangeCurrentUICultureInfo(string locale)
{
CultureInfo cultureInfo = ConvertCultureInfo(locale);
if (cultureInfo != null)
{
CultureInfo.CurrentUICulture = cultureInfo;
}
else
{
Log.Error(LogTag, "CultureInfo is null. locale: " + locale);
}
}
private bool ExistCultureInfo(string locale)
{
foreach (var cultureInfo in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
if (cultureInfo.Name == locale)
{
return true;
}
}
return false;
}
private CultureInfo GetCultureInfo(string locale)
{
if (!ExistCultureInfo(locale))
{
return null;
}
try
{
return new CultureInfo(locale);
}
catch (CultureNotFoundException)
{
return null;
}
}
private CultureInfo GetFallbackCultureInfo(ULocale uLocale)
{
CultureInfo fallbackCultureInfo = null;
string locale = string.Empty;
if (uLocale.Locale != null)
{
locale = uLocale.Locale.Replace("_", "-");
fallbackCultureInfo = GetCultureInfo(locale);
}
if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Script != null && uLocale.Country != null)
{
locale = uLocale.Language + "-" + uLocale.Script + "-" + uLocale.Country;
fallbackCultureInfo = GetCultureInfo(locale);
}
if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Script != null)
{
locale = uLocale.Language + "-" + uLocale.Script;
fallbackCultureInfo = GetCultureInfo(locale);
}
if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Country != null)
{
locale = uLocale.Language + "-" + uLocale.Country;
fallbackCultureInfo = GetCultureInfo(locale);
}
if (fallbackCultureInfo == null && uLocale.Language != null)
{
locale = uLocale.Language;
fallbackCultureInfo = GetCultureInfo(locale);
}
if (fallbackCultureInfo == null)
{
try
{
fallbackCultureInfo = new CultureInfo("en");
}
catch (CultureNotFoundException e)
{
Log.Error(LogTag, "Failed to create CultureInfo. err = " + e.Message);
}
}
return fallbackCultureInfo;
}
}
internal static class GlobalizationMode
{
private static int _invariant = -1;
internal static bool Invariant
{
get
{
if (_invariant == -1)
{
string value = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT");
_invariant = value != null ? (value.Equals("1") ? 1 : 0) : 0;
}
return _invariant != 0;
}
}
}
internal class ULocale
{
private const int ULOC_FULLNAME_CAPACITY = 157;
private const int ULOC_LANG_CAPACITY = 12;
private const int ULOC_SCRIPT_CAPACITY = 6;
private const int ULOC_COUNTRY_CAPACITY = 4;
private const int ULOC_VARIANT_CAPACITY = ULOC_FULLNAME_CAPACITY;
internal ULocale(string locale)
{
Locale = Canonicalize(locale);
Language = GetLanguage(Locale);
Script = GetScript(Locale);
Country = GetCountry(Locale);
Variant = GetVariant(Locale);
LCID = GetLCID(Locale);
}
internal string Locale { get; private set; }
internal string Language { get; private set; }
internal string Script { get; private set; }
internal string Country { get; private set; }
internal string Variant { get; private set; }
internal int LCID { get; private set; }
private string Canonicalize(string localeName)
{
// Get the locale name from ICU
StringBuilder sb = new StringBuilder(ULOC_FULLNAME_CAPACITY);
if (Interop.BaseUtilsi18n.Canonicalize(localeName, sb, sb.Capacity) <= 0)
{
return null;
}
return sb.ToString();
}
private string GetLanguage(string locale)
{
// Get the language name from ICU
StringBuilder sb = new StringBuilder(ULOC_LANG_CAPACITY);
if (Interop.BaseUtilsi18n.GetLanguage(locale, sb, sb.Capacity, out int bufSizeLanguage) != 0)
{
return null;
}
return sb.ToString();
}
private string GetScript(string locale)
{
// Get the script name from ICU
StringBuilder sb = new StringBuilder(ULOC_SCRIPT_CAPACITY);
if (Interop.BaseUtilsi18n.GetScript(locale, sb, sb.Capacity) <= 0)
{
return null;
}
return sb.ToString();
}
private string GetCountry(string locale)
{
int err = 0;
// Get the country name from ICU
StringBuilder sb = new StringBuilder(ULOC_COUNTRY_CAPACITY);
if (Interop.BaseUtilsi18n.GetCountry(locale, sb, sb.Capacity, out err) <= 0)
{
return null;
}
return sb.ToString();
}
private string GetVariant(string locale)
{
// Get the variant name from ICU
StringBuilder sb = new StringBuilder(ULOC_VARIANT_CAPACITY);
if (Interop.BaseUtilsi18n.GetVariant(locale, sb, sb.Capacity) <= 0)
{
return null;
}
return sb.ToString();
}
private int GetLCID(string locale)
{
// Get the LCID from ICU
uint lcid = Interop.BaseUtilsi18n.GetLCID(locale);
return (int)lcid;
}
internal static string GetDefaultLocale()
{
IntPtr stringPtr = Interop.Libc.GetEnvironmentVariable("LANG");
if (stringPtr == IntPtr.Zero)
{
return string.Empty;
}
return Marshal.PtrToStringAnsi(stringPtr);
}
}
}