Add new internal APIs for UIThread model (#4346)
[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             double interval = new Random().Next(10 * 1000);
223             if (interval <= 0)
224                 interval = 10 * 1000;
225
226             sTimer = new System.Timers.Timer(interval);
227             sTimer.Elapsed += OnTimedEvent;
228             sTimer.AutoReset = false;
229             sTimer.Enabled = true;
230         }
231
232         private static void OnTimedEvent(Object source, ElapsedEventArgs e)
233         {
234             System.GC.Collect();
235         }
236
237         /// <summary>
238         /// Overrides this method if want to handle behavior when the system battery is low.
239         /// If base.OnLowBattery() is not called, the event 'LowBattery' will not be emitted.
240         /// </summary>
241         /// <param name="e">The low battery event argument</param>
242         /// <since_tizen> 3 </since_tizen>
243         protected virtual void OnLowBattery(LowBatteryEventArgs e)
244         {
245             LowBattery?.Invoke(this, e);
246         }
247
248         /// <summary>
249         /// Overrides this method if want to handle behavior when the system language is changed.
250         /// If base.OnLocaleChanged() is not called, the event 'LocaleChanged' will not be emitted.
251         /// </summary>
252         /// <param name="e">The locale changed event argument</param>
253         /// <since_tizen> 3 </since_tizen>
254         protected virtual void OnLocaleChanged(LocaleChangedEventArgs e)
255         {
256             if (!GlobalizationMode.Invariant)
257             {
258                 ChangeCurrentUICultureInfo(e.Locale);
259             }
260
261             LocaleChanged?.Invoke(this, e);
262         }
263
264         /// <summary>
265         /// Overrides this method if want to handle behavior when the region format is changed.
266         /// If base.OnRegionFormatChanged() is not called, the event 'RegionFormatChanged' will not be emitted.
267         /// </summary>
268         /// <param name="e">The region format changed event argument</param>
269         /// <since_tizen> 3 </since_tizen>
270         protected virtual void OnRegionFormatChanged(RegionFormatChangedEventArgs e)
271         {
272             if (!GlobalizationMode.Invariant)
273             {
274                 ChangeCurrentCultureInfo(e.Region);
275             }
276
277             RegionFormatChanged?.Invoke(this, e);
278         }
279
280         /// <summary>
281         /// Overrides this method if want to handle behavior when the device orientation is changed.
282         /// If base.OnRegionFormatChanged() is not called, the event 'RegionFormatChanged' will not be emitted.
283         /// </summary>
284         /// <param name="e">The device orientation changed event argument</param>
285         /// <since_tizen> 3 </since_tizen>
286         protected virtual void OnDeviceOrientationChanged(DeviceOrientationEventArgs e)
287         {
288             DeviceOrientationChanged?.Invoke(this, e);
289         }
290
291         /// <summary>
292         /// Dispatches an asynchronous message to the main loop of the CoreTask.
293         /// </summary>
294         /// <param name="runner">The runner callaback.</param>
295         /// <exception cref="ArgumentNullException">Thrown when the runner is null.</exception>
296         /// <since_tizen> 10 </since_tizen>
297         [EditorBrowsable(EditorBrowsableState.Never)]
298         public void Post(Action runner)
299         {
300             if (runner == null)
301             {
302                 throw new ArgumentNullException(nameof(runner));
303             }
304
305             GSourceManager.Post(runner);
306         }
307
308         /// <summary>
309         /// Dispatches an asynchronous message to the main loop of the CoreTask.
310         /// </summary>
311         /// <typeparam name="T">The type of the result.</typeparam>
312         /// <param name="runner">The runner callback.</param>
313         /// <exception cref="ArgumentNullException">Thrown when the runner is null.</exception>
314         /// <returns>A task with the result.</returns>
315         /// <since_tizen> 10 </since_tizen>
316         [EditorBrowsable(EditorBrowsableState.Never)]
317         public async Task<T> Post<T>(Func<T> runner)
318         {
319             if (runner == null)
320             {
321                 throw new ArgumentNullException(nameof(runner));
322             }
323
324             var task = new TaskCompletionSource<T>();
325             GSourceManager.Post(() => { task.SetResult(runner()); });
326             return await task.Task.ConfigureAwait(false);
327         }
328
329         /// <summary>
330         /// Releases any unmanaged resources used by this object. Can also dispose any other disposable objects.
331         /// </summary>
332         /// <param name="disposing">If true, disposes any disposable objects. If false, does not dispose disposable objects.</param>
333         /// <since_tizen> 3 </since_tizen>
334         protected override void Dispose(bool disposing)
335         {
336             if (!_disposedValue)
337             {
338                 if (disposing)
339                 {
340                     _backend.Dispose();
341                 }
342
343                 _disposedValue = true;
344             }
345             base.Dispose(disposing);
346         }
347
348         private CultureInfo ConvertCultureInfo(string locale)
349         {
350             ULocale pLocale = new ULocale(locale);
351             string cultureName = CultureInfoHelper.GetCultureName(pLocale.Locale.Replace("_", "-"));
352
353             if (!string.IsNullOrEmpty(cultureName))
354             {
355                 try
356                 {
357                     return new CultureInfo(cultureName);
358                 }
359                 catch (CultureNotFoundException)
360                 {
361                     Log.Error(LogTag, "CultureNotFoundException occurs. CultureName: " + cultureName);
362                 }
363             }
364
365             try
366             {
367                 return new CultureInfo(pLocale.LCID);
368             }
369             catch (ArgumentOutOfRangeException)
370             {
371                 return GetFallbackCultureInfo(pLocale);
372             }
373             catch (CultureNotFoundException)
374             {
375                 return GetFallbackCultureInfo(pLocale);
376             }
377         }
378
379         private void ChangeCurrentCultureInfo(string locale)
380         {
381             CultureInfo cultureInfo = ConvertCultureInfo(locale);
382             if (cultureInfo != null)
383             {
384                 CultureInfo.CurrentCulture = cultureInfo;
385             }
386             else
387             {
388                 Log.Error(LogTag, "CultureInfo is null. locale: " + locale);
389             }
390         }
391
392         private void ChangeCurrentUICultureInfo(string locale)
393         {
394             CultureInfo cultureInfo = ConvertCultureInfo(locale);
395             if (cultureInfo != null)
396             {
397                 CultureInfo.CurrentUICulture = cultureInfo;
398             }
399             else
400             {
401                 Log.Error(LogTag, "CultureInfo is null. locale: " + locale);
402             }
403         }
404
405         private bool ExistCultureInfo(string locale)
406         {
407             foreach (var cultureInfo in CultureInfo.GetCultures(CultureTypes.AllCultures))
408             {
409                 if (cultureInfo.Name == locale)
410                 {
411                     return true;
412                 }
413             }
414
415             return false;
416         }
417
418         private CultureInfo GetCultureInfo(string locale)
419         {
420             if (!ExistCultureInfo(locale))
421             {
422                 return null;
423             }
424
425             try
426             {
427                 return new CultureInfo(locale);
428             }
429             catch (CultureNotFoundException)
430             {
431                 return null;
432             }
433         }
434
435         private CultureInfo GetFallbackCultureInfo(ULocale uLocale)
436         {
437             CultureInfo fallbackCultureInfo = null;
438             string locale = string.Empty;
439
440             if (uLocale.Locale != null)
441             {
442                 locale = uLocale.Locale.Replace("_", "-");
443                 fallbackCultureInfo = GetCultureInfo(locale);
444             }
445
446             if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Script != null && uLocale.Country != null)
447             {
448                 locale = uLocale.Language + "-" + uLocale.Script + "-" + uLocale.Country;
449                 fallbackCultureInfo = GetCultureInfo(locale);
450             }
451
452             if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Script != null)
453             {
454                 locale = uLocale.Language + "-" + uLocale.Script;
455                 fallbackCultureInfo = GetCultureInfo(locale);
456             }
457
458             if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Country != null)
459             {
460                 locale = uLocale.Language + "-" + uLocale.Country;
461                 fallbackCultureInfo = GetCultureInfo(locale);
462             }
463
464             if (fallbackCultureInfo == null && uLocale.Language != null)
465             {
466                 locale = uLocale.Language;
467                 fallbackCultureInfo = GetCultureInfo(locale);
468             }
469
470             if (fallbackCultureInfo == null)
471             {
472                 try
473                 {
474                     fallbackCultureInfo = new CultureInfo("en");
475                 }
476                 catch (CultureNotFoundException e)
477                 {
478                     Log.Error(LogTag, "Failed to create CultureInfo. err = " + e.Message);
479                 }
480             }
481
482             return fallbackCultureInfo;
483         }
484     }
485
486     internal static class GlobalizationMode
487     {
488         private static int _invariant = -1;
489
490         internal static bool Invariant
491         {
492             get
493             {
494                 if (_invariant == -1)
495                 {
496                     string value = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT");
497                     _invariant = value != null ? (value.Equals("1") ? 1 : 0) : 0;
498                 }
499
500                 return _invariant != 0;
501             }
502         }
503     }
504
505     internal class ULocale
506     {
507         private const int ULOC_FULLNAME_CAPACITY = 157;
508         private const int ULOC_LANG_CAPACITY = 12;
509         private const int ULOC_SCRIPT_CAPACITY = 6;
510         private const int ULOC_COUNTRY_CAPACITY = 4;
511         private const int ULOC_VARIANT_CAPACITY = ULOC_FULLNAME_CAPACITY;
512
513         internal ULocale(string locale)
514         {
515             Locale = Canonicalize(locale);
516             Language = GetLanguage(Locale);
517             Script = GetScript(Locale);
518             Country = GetCountry(Locale);
519             Variant = GetVariant(Locale);
520             LCID = GetLCID(Locale);
521         }
522
523         internal string Locale { get; private set; }
524         internal string Language { get; private set; }
525         internal string Script { get; private set; }
526         internal string Country { get; private set; }
527         internal string Variant { get; private set; }
528         internal int LCID { get; private set; }
529
530         private string Canonicalize(string localeName)
531         {
532             // Get the locale name from ICU
533             StringBuilder sb = new StringBuilder(ULOC_FULLNAME_CAPACITY);
534             if (Interop.BaseUtilsi18n.Canonicalize(localeName, sb, sb.Capacity) <= 0)
535             {
536                 return null;
537             }
538
539             return sb.ToString();
540         }
541
542         private string GetLanguage(string locale)
543         {
544             // Get the language name from ICU
545             StringBuilder sb = new StringBuilder(ULOC_LANG_CAPACITY);
546             if (Interop.BaseUtilsi18n.GetLanguage(locale, sb, sb.Capacity, out int bufSizeLanguage) != 0)
547             {
548                 return null;
549             }
550
551             return sb.ToString();
552         }
553
554         private string GetScript(string locale)
555         {
556             // Get the script name from ICU
557             StringBuilder sb = new StringBuilder(ULOC_SCRIPT_CAPACITY);
558             if (Interop.BaseUtilsi18n.GetScript(locale, sb, sb.Capacity) <= 0)
559             {
560                 return null;
561             }
562
563             return sb.ToString();
564         }
565
566         private string GetCountry(string locale)
567         {
568             int err = 0;
569
570             // Get the country name from ICU
571             StringBuilder sb = new StringBuilder(ULOC_COUNTRY_CAPACITY);
572             if (Interop.BaseUtilsi18n.GetCountry(locale, sb, sb.Capacity, out err) <= 0)
573             {
574                 return null;
575             }
576
577             return sb.ToString();
578         }
579
580         private string GetVariant(string locale)
581         {
582             // Get the variant name from ICU
583             StringBuilder sb = new StringBuilder(ULOC_VARIANT_CAPACITY);
584             if (Interop.BaseUtilsi18n.GetVariant(locale, sb, sb.Capacity) <= 0)
585             {
586                 return null;
587             }
588
589             return sb.ToString();
590         }
591
592         private int GetLCID(string locale)
593         {
594             // Get the LCID from ICU
595             uint lcid = Interop.BaseUtilsi18n.GetLCID(locale);
596             return (int)lcid;
597         }
598
599         internal static string GetDefaultLocale()
600         {
601             IntPtr stringPtr = IntPtr.Zero;
602             if (Interop.BaseUtilsi18n.GetDefault(out stringPtr) != 0)
603             {
604                 return string.Empty;
605             }
606
607             if (stringPtr == IntPtr.Zero)
608             {
609                 return string.Empty;
610             }
611
612             return Marshal.PtrToStringAnsi(stringPtr);
613         }
614     }
615 }