[Applications.Common] Add TimeZoneChanged Event (#5527)
[platform/core/csapi/tizenfx.git] / src / Tizen.Applications.Common / Tizen.Applications / CoreApplication.cs
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 using System;
18 using System.Collections.Concurrent;
19 using System.ComponentModel;
20 using System.Globalization;
21 using System.Runtime.InteropServices;
22 using System.Text;
23 using System.Threading;
24 using System.Threading.Tasks;
25 using System.Timers;
26 using Tizen.Applications.CoreBackend;
27
28 namespace Tizen.Applications
29 {
30     /// <summary>
31     /// This class represents an application controlled lifecycles by the backend system.
32     /// </summary>
33     /// <since_tizen> 3 </since_tizen>
34     public class CoreApplication : Application
35     {
36         private readonly ICoreBackend _backend;
37         private readonly ICoreTask _task;
38         private bool _disposedValue = false;
39
40         /// <summary>
41         /// Initializes the CoreApplication class.
42         /// </summary>
43         /// <param name="backend">The backend instance implementing ICoreBacked interface.</param>
44         /// <since_tizen> 3 </since_tizen>
45         public CoreApplication(ICoreBackend backend)
46         {
47             _backend = backend;
48             _task = null;
49         }
50
51         /// <summary>
52         /// Initializes the CoreApplication class.
53         /// </summary>
54         /// <param name="backend">The backend instance implementing ICoreBackend interface.</param>
55         /// <param name="task">The backend instance implmenting ICoreTask interface.</param>
56         /// <since_tizen> 10 </since_tizen>
57         [EditorBrowsable(EditorBrowsableState.Never)]
58         public CoreApplication(ICoreBackend backend, ICoreTask task)
59         {
60             _backend = backend;
61             _task = task;
62         }
63
64         /// <summary>
65         /// Occurs when the application is launched.
66         /// </summary>
67         /// <since_tizen> 3 </since_tizen>
68         public event EventHandler Created;
69
70         /// <summary>
71         /// Occurs when the application is about to shutdown.
72         /// </summary>
73         /// <since_tizen> 3 </since_tizen>
74         public event EventHandler Terminated;
75
76         /// <summary>
77         /// Occurs whenever the application receives the appcontrol message.
78         /// </summary>
79         /// <since_tizen> 3 </since_tizen>
80         public event EventHandler<AppControlReceivedEventArgs> AppControlReceived;
81
82         /// <summary>
83         /// Occurs when the system memory is low.
84         /// </summary>
85         /// <since_tizen> 3 </since_tizen>
86         public event EventHandler<LowMemoryEventArgs> LowMemory;
87
88         /// <summary>
89         /// Occurs when the system battery is low.
90         /// </summary>
91         /// <since_tizen> 3 </since_tizen>
92         public event EventHandler<LowBatteryEventArgs> LowBattery;
93
94         /// <summary>
95         /// Occurs when the system language is chagned.
96         /// </summary>
97         /// <since_tizen> 3 </since_tizen>
98         public event EventHandler<LocaleChangedEventArgs> LocaleChanged;
99
100         /// <summary>
101         /// Occurs when the region format is changed.
102         /// </summary>
103         /// <since_tizen> 3 </since_tizen>
104         public event EventHandler<RegionFormatChangedEventArgs> RegionFormatChanged;
105
106         /// <summary>
107         /// Occurs when the device orientation is changed.
108         /// </summary>
109         /// <since_tizen> 3 </since_tizen>
110         public event EventHandler<DeviceOrientationEventArgs> DeviceOrientationChanged;
111
112         /// <summary>
113         /// Occurs when the time zone is changed.
114         /// </summary>
115         /// <since_tizen> 11 </since_tizen>
116         public event EventHandler<TimeZoneChangedEventArgs> TimeZoneChanged;
117
118         /// <summary>
119         /// The backend instance.
120         /// </summary>
121         /// <since_tizen> 3 </since_tizen>
122         protected ICoreBackend Backend { get { return _backend; } }
123
124         /// <summary>
125         /// Runs the application's main loop.
126         /// </summary>
127         /// <param name="args">Arguments from commandline.</param>
128         /// <since_tizen> 3 </since_tizen>
129         public override void Run(string[] args)
130         {
131             base.Run(args);
132
133             _backend.AddEventHandler(EventType.Created, OnCreate);
134             _backend.AddEventHandler(EventType.Terminated, OnTerminate);
135             _backend.AddEventHandler<AppControlReceivedEventArgs>(EventType.AppControlReceived, OnAppControlReceived);
136             _backend.AddEventHandler<LowMemoryEventArgs>(EventType.LowMemory, OnLowMemory);
137             _backend.AddEventHandler<LowBatteryEventArgs>(EventType.LowBattery, OnLowBattery);
138             _backend.AddEventHandler<LocaleChangedEventArgs>(EventType.LocaleChanged, OnLocaleChanged);
139             _backend.AddEventHandler<RegionFormatChangedEventArgs>(EventType.RegionFormatChanged, OnRegionFormatChanged);
140             _backend.AddEventHandler<DeviceOrientationEventArgs>(EventType.DeviceOrientationChanged, OnDeviceOrientationChanged);
141             _backend.AddEventHandler<TimeZoneChangedEventArgs>(EventType.TimeZoneChanged, OnTimeZoneChanged);
142
143             string[] argsClone = new string[args == null ? 1 : args.Length + 1];
144             if (args != null && args.Length > 1)
145             {
146                 args.CopyTo(argsClone, 1);
147             }
148             argsClone[0] = string.Empty;
149
150             if (_task != null)
151             {
152                 ICoreTaskBackend backend = (ICoreTaskBackend)_backend;
153                 backend.SetCoreTask(_task);
154                 backend.Run(argsClone);
155             }
156             else
157             {
158                 _backend.Run(argsClone);
159             }
160         }
161
162         /// <summary>
163         /// Exits the main loop of the application.
164         /// </summary>
165         /// <since_tizen> 3 </since_tizen>
166         public override void Exit()
167         {
168             _backend.Exit();
169         }
170
171         /// <summary>
172         /// Overrides this method if want to handle behavior when the application is launched.
173         /// If base.OnCreated() is not called, the event 'Created' will not be emitted.
174         /// </summary>
175         /// <since_tizen> 3 </since_tizen>
176         protected virtual void OnCreate()
177         {
178             if (_task != null)
179             {
180                 TizenUISynchronizationContext.Initialize();
181             }
182
183             if (!GlobalizationMode.Invariant)
184             {
185                 string locale = ULocale.GetDefaultLocale();
186                 ChangeCurrentUICultureInfo(locale);
187                 ChangeCurrentCultureInfo(locale);
188             }
189             else
190             {
191                 Log.Warn(LogTag, "Run in invariant mode");
192             }
193
194             Created?.Invoke(this, EventArgs.Empty);
195         }
196
197         /// <summary>
198         /// Overrides this method if want to handle behavior when the application is terminated.
199         /// If base.OnTerminate() is not called, the event 'Terminated' will not be emitted.
200         /// </summary>
201         /// <since_tizen> 3 </since_tizen>
202         protected virtual void OnTerminate()
203         {
204             Terminated?.Invoke(this, EventArgs.Empty);
205         }
206
207         /// <summary>
208         /// Overrides this method if want to handle behavior when the application receives the appcontrol message.
209         /// If base.OnAppControlReceived() is not called, the event 'AppControlReceived' will not be emitted.
210         /// </summary>
211         /// <param name="e"></param>
212         /// <since_tizen> 3 </since_tizen>
213         protected virtual void OnAppControlReceived(AppControlReceivedEventArgs e)
214         {
215             if (e == null)
216             {
217                 Log.Error(LogTag, "e is null");
218                 return;
219             }
220
221             AppControlReceived?.Invoke(this, e);
222         }
223
224         /// <summary>
225         /// Overrides this method if want to handle behavior when the system memory is low.
226         /// If base.OnLowMemory() is not called, the event 'LowMemory' will not be emitted.
227         /// </summary>
228         /// <param name="e">The low memory event argument</param>
229         /// <since_tizen> 3 </since_tizen>
230         protected virtual void OnLowMemory(LowMemoryEventArgs e)
231         {
232             if (e == null)
233             {
234                 Log.Error(LogTag, "e is null");
235                 return;
236             }
237
238             LowMemory?.Invoke(this, e);
239             if (e.LowMemoryStatus == LowMemoryStatus.SoftWarning || e.LowMemoryStatus == LowMemoryStatus.HardWarning)
240             {
241                 System.GC.Collect();
242             }
243         }
244
245         /// <summary>
246         /// Overrides this method if want to handle behavior when the system battery is low.
247         /// If base.OnLowBattery() is not called, the event 'LowBattery' will not be emitted.
248         /// </summary>
249         /// <param name="e">The low battery event argument</param>
250         /// <since_tizen> 3 </since_tizen>
251         protected virtual void OnLowBattery(LowBatteryEventArgs e)
252         {
253             if (e == null)
254             {
255                 Log.Error(LogTag, "e is null");
256                 return;
257             }
258
259             LowBattery?.Invoke(this, e);
260         }
261
262         /// <summary>
263         /// Overrides this method if want to handle behavior when the system language is changed.
264         /// If base.OnLocaleChanged() is not called, the event 'LocaleChanged' will not be emitted.
265         /// </summary>
266         /// <param name="e">The locale changed event argument</param>
267         /// <since_tizen> 3 </since_tizen>
268         protected virtual void OnLocaleChanged(LocaleChangedEventArgs e)
269         {
270             if (e == null)
271             {
272                 Log.Error(LogTag, "e is null");
273                 return;
274             }
275
276             if (!GlobalizationMode.Invariant)
277             {
278                 ChangeCurrentUICultureInfo(e.Locale);
279             }
280
281             LocaleChanged?.Invoke(this, e);
282         }
283
284         /// <summary>
285         /// Overrides this method if want to handle behavior when the region format is changed.
286         /// If base.OnRegionFormatChanged() is not called, the event 'RegionFormatChanged' will not be emitted.
287         /// </summary>
288         /// <param name="e">The region format changed event argument</param>
289         /// <since_tizen> 3 </since_tizen>
290         protected virtual void OnRegionFormatChanged(RegionFormatChangedEventArgs e)
291         {
292             if (e == null)
293             {
294                 Log.Error(LogTag, "e is null");
295                 return;
296             }
297
298             if (!GlobalizationMode.Invariant)
299             {
300                 ChangeCurrentCultureInfo(e.Region);
301             }
302
303             RegionFormatChanged?.Invoke(this, e);
304         }
305
306         /// <summary>
307         /// Overrides this method if want to handle behavior when the device orientation is changed.
308         /// If base.OnRegionFormatChanged() is not called, the event 'RegionFormatChanged' will not be emitted.
309         /// </summary>
310         /// <param name="e">The device orientation changed event argument</param>
311         /// <since_tizen> 3 </since_tizen>
312         protected virtual void OnDeviceOrientationChanged(DeviceOrientationEventArgs e)
313         {
314             DeviceOrientationChanged?.Invoke(this, e);
315         }
316
317         /// <summary>
318         /// Override this method if you want to handle behavior when the time zone is changed.
319         /// If base.OnTimeZoneChanged() is not called, the event "TimeZoneChanged" will not be emitted.
320         /// </summary>
321         /// <param name="e">The time zone changed event argument</param>
322         /// <since_tizen> 11 </since_tizen>
323         protected virtual void OnTimeZoneChanged(TimeZoneChangedEventArgs e)
324         {
325             TimeZoneChanged?.Invoke(this, e);
326         }
327
328         /// <summary>
329         /// Dispatches an asynchronous message to a main loop of the CoreApplication.
330         /// </summary>
331         /// <remarks>
332         /// If an application uses UI thread App Model, the asynchronous message will be delivered to the UI thread.
333         /// If not, the asynchronous message will be delivered to the main thread.
334         /// </remarks>
335         /// <param name="runner">The runner callaback.</param>
336         /// <exception cref="ArgumentNullException">Thrown when the runner is null.</exception>
337         /// <since_tizen> 10 </since_tizen>
338         [EditorBrowsable(EditorBrowsableState.Never)]
339         public static void Post(Action runner)
340         {
341             if (runner == null)
342             {
343                 throw new ArgumentNullException(nameof(runner));
344             }
345
346             GSourceManager.Post(runner, true);
347         }
348
349         /// <summary>
350         /// Dispatches an asynchronous message to a main loop of the CoreApplication.
351         /// </summary>
352         /// <remarks>
353         /// If an application uses UI thread App Model, the asynchronous message will be delivered to the UI thread.
354         /// If not, the asynchronous message will be delivered to the main thread.
355         /// </remarks>
356         /// <typeparam name="T">The type of the result.</typeparam>
357         /// <param name="runner">The runner callback.</param>
358         /// <exception cref="ArgumentNullException">Thrown when the runner is null.</exception>
359         /// <returns>A task with the result.</returns>
360         /// <since_tizen> 10 </since_tizen>
361         [EditorBrowsable(EditorBrowsableState.Never)]
362         public static async Task<T> Post<T>(Func<T> runner)
363         {
364             if (runner == null)
365             {
366                 throw new ArgumentNullException(nameof(runner));
367             }
368
369             var task = new TaskCompletionSource<T>();
370             GSourceManager.Post(() => { task.SetResult(runner()); }, true);
371             return await task.Task.ConfigureAwait(false);
372         }
373
374         /// <summary>
375         /// Releases any unmanaged resources used by this object. Can also dispose any other disposable objects.
376         /// </summary>
377         /// <param name="disposing">If true, disposes any disposable objects. If false, does not dispose disposable objects.</param>
378         /// <since_tizen> 3 </since_tizen>
379         protected override void Dispose(bool disposing)
380         {
381             if (!_disposedValue)
382             {
383                 if (disposing)
384                 {
385                     _backend.Dispose();
386                 }
387
388                 _disposedValue = true;
389             }
390             base.Dispose(disposing);
391         }
392
393         private CultureInfo ConvertCultureInfo(string locale)
394         {
395             ULocale pLocale = new ULocale(locale);
396             string cultureName = CultureInfoHelper.GetCultureName(pLocale.Locale.Replace("_", "-"));
397
398             if (!string.IsNullOrEmpty(cultureName))
399             {
400                 try
401                 {
402                     return new CultureInfo(cultureName);
403                 }
404                 catch (CultureNotFoundException)
405                 {
406                     Log.Error(LogTag, "CultureNotFoundException occurs. CultureName: " + cultureName);
407                 }
408             }
409
410             try
411             {
412                 return new CultureInfo(pLocale.LCID);
413             }
414             catch (ArgumentOutOfRangeException)
415             {
416                 return GetFallbackCultureInfo(pLocale);
417             }
418             catch (CultureNotFoundException)
419             {
420                 return GetFallbackCultureInfo(pLocale);
421             }
422         }
423
424         private void ChangeCurrentCultureInfo(string locale)
425         {
426             CultureInfo cultureInfo = ConvertCultureInfo(locale);
427             if (cultureInfo != null)
428             {
429                 CultureInfo.CurrentCulture = cultureInfo;
430             }
431             else
432             {
433                 Log.Error(LogTag, "CultureInfo is null. locale: " + locale);
434             }
435         }
436
437         private void ChangeCurrentUICultureInfo(string locale)
438         {
439             CultureInfo cultureInfo = ConvertCultureInfo(locale);
440             if (cultureInfo != null)
441             {
442                 CultureInfo.CurrentUICulture = cultureInfo;
443             }
444             else
445             {
446                 Log.Error(LogTag, "CultureInfo is null. locale: " + locale);
447             }
448         }
449
450         private bool ExistCultureInfo(string locale)
451         {
452             foreach (var cultureInfo in CultureInfo.GetCultures(CultureTypes.AllCultures))
453             {
454                 if (cultureInfo.Name == locale)
455                 {
456                     return true;
457                 }
458             }
459
460             return false;
461         }
462
463         private CultureInfo GetCultureInfo(string locale)
464         {
465             if (!ExistCultureInfo(locale))
466             {
467                 return null;
468             }
469
470             try
471             {
472                 return new CultureInfo(locale);
473             }
474             catch (CultureNotFoundException)
475             {
476                 return null;
477             }
478         }
479
480         private CultureInfo GetFallbackCultureInfo(ULocale uLocale)
481         {
482             CultureInfo fallbackCultureInfo = null;
483             string locale = string.Empty;
484
485             if (uLocale.Locale != null)
486             {
487                 locale = uLocale.Locale.Replace("_", "-");
488                 fallbackCultureInfo = GetCultureInfo(locale);
489             }
490
491             if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Script != null && uLocale.Country != null)
492             {
493                 locale = uLocale.Language + "-" + uLocale.Script + "-" + uLocale.Country;
494                 fallbackCultureInfo = GetCultureInfo(locale);
495             }
496
497             if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Script != null)
498             {
499                 locale = uLocale.Language + "-" + uLocale.Script;
500                 fallbackCultureInfo = GetCultureInfo(locale);
501             }
502
503             if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Country != null)
504             {
505                 locale = uLocale.Language + "-" + uLocale.Country;
506                 fallbackCultureInfo = GetCultureInfo(locale);
507             }
508
509             if (fallbackCultureInfo == null && uLocale.Language != null)
510             {
511                 locale = uLocale.Language;
512                 fallbackCultureInfo = GetCultureInfo(locale);
513             }
514
515             if (fallbackCultureInfo == null)
516             {
517                 try
518                 {
519                     fallbackCultureInfo = new CultureInfo("en");
520                 }
521                 catch (CultureNotFoundException e)
522                 {
523                     Log.Error(LogTag, "Failed to create CultureInfo. err = " + e.Message);
524                 }
525             }
526
527             return fallbackCultureInfo;
528         }
529     }
530
531     internal static class GlobalizationMode
532     {
533         private static int _invariant = -1;
534
535         internal static bool Invariant
536         {
537             get
538             {
539                 if (_invariant == -1)
540                 {
541                     string value = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT");
542                     _invariant = value != null ? (value.Equals("1") ? 1 : 0) : 0;
543                 }
544
545                 return _invariant != 0;
546             }
547         }
548     }
549
550     internal class ULocale
551     {
552         private const int ULOC_FULLNAME_CAPACITY = 157;
553         private const int ULOC_LANG_CAPACITY = 12;
554         private const int ULOC_SCRIPT_CAPACITY = 6;
555         private const int ULOC_COUNTRY_CAPACITY = 4;
556         private const int ULOC_VARIANT_CAPACITY = ULOC_FULLNAME_CAPACITY;
557
558         internal ULocale(string locale)
559         {
560             Locale = Canonicalize(locale);
561             Language = GetLanguage(Locale);
562             Script = GetScript(Locale);
563             Country = GetCountry(Locale);
564             Variant = GetVariant(Locale);
565             LCID = GetLCID(Locale);
566         }
567
568         internal string Locale { get; private set; }
569         internal string Language { get; private set; }
570         internal string Script { get; private set; }
571         internal string Country { get; private set; }
572         internal string Variant { get; private set; }
573         internal int LCID { get; private set; }
574
575         private string Canonicalize(string localeName)
576         {
577             // Get the locale name from ICU
578             StringBuilder sb = new StringBuilder(ULOC_FULLNAME_CAPACITY);
579             if (Interop.BaseUtilsi18n.Canonicalize(localeName, sb, sb.Capacity) <= 0)
580             {
581                 return null;
582             }
583
584             return sb.ToString();
585         }
586
587         private string GetLanguage(string locale)
588         {
589             // Get the language name from ICU
590             StringBuilder sb = new StringBuilder(ULOC_LANG_CAPACITY);
591             if (Interop.BaseUtilsi18n.GetLanguage(locale, sb, sb.Capacity, out int bufSizeLanguage) != 0)
592             {
593                 return null;
594             }
595
596             return sb.ToString();
597         }
598
599         private string GetScript(string locale)
600         {
601             // Get the script name from ICU
602             StringBuilder sb = new StringBuilder(ULOC_SCRIPT_CAPACITY);
603             if (Interop.BaseUtilsi18n.GetScript(locale, sb, sb.Capacity) <= 0)
604             {
605                 return null;
606             }
607
608             return sb.ToString();
609         }
610
611         private string GetCountry(string locale)
612         {
613             int err = 0;
614
615             // Get the country name from ICU
616             StringBuilder sb = new StringBuilder(ULOC_COUNTRY_CAPACITY);
617             if (Interop.BaseUtilsi18n.GetCountry(locale, sb, sb.Capacity, out err) <= 0)
618             {
619                 return null;
620             }
621
622             return sb.ToString();
623         }
624
625         private string GetVariant(string locale)
626         {
627             // Get the variant name from ICU
628             StringBuilder sb = new StringBuilder(ULOC_VARIANT_CAPACITY);
629             if (Interop.BaseUtilsi18n.GetVariant(locale, sb, sb.Capacity) <= 0)
630             {
631                 return null;
632             }
633
634             return sb.ToString();
635         }
636
637         private int GetLCID(string locale)
638         {
639             // Get the LCID from ICU
640             uint lcid = Interop.BaseUtilsi18n.GetLCID(locale);
641             return (int)lcid;
642         }
643
644         internal static string GetDefaultLocale()
645         {
646             IntPtr stringPtr = Interop.Libc.GetEnvironmentVariable("LANG");
647             if (stringPtr == IntPtr.Zero)
648             {
649                 return string.Empty;
650             }
651
652             return Marshal.PtrToStringAnsi(stringPtr);
653         }
654     }
655 }