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