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