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