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