[Applications.Common] Add Globalization invariant mode check (#3998)
[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             if (!GlobalizationMode.Invariant)
146             {
147                 string locale = ULocale.GetDefaultLocale();
148                 ChangeCurrentUICultureInfo(locale);
149                 ChangeCurrentCultureInfo(locale);
150             }
151             else
152             {
153                 Log.Warn(LogTag, "Run in invariant mode");
154             }
155
156             Created?.Invoke(this, EventArgs.Empty);
157         }
158
159         /// <summary>
160         /// Overrides this method if want to handle behavior when the application is terminated.
161         /// If base.OnTerminate() is not called, the event 'Terminated' will not be emitted.
162         /// </summary>
163         /// <since_tizen> 3 </since_tizen>
164         protected virtual void OnTerminate()
165         {
166             Terminated?.Invoke(this, EventArgs.Empty);
167         }
168
169         /// <summary>
170         /// Overrides this method if want to handle behavior when the application receives the appcontrol message.
171         /// If base.OnAppControlReceived() is not called, the event 'AppControlReceived' will not be emitted.
172         /// </summary>
173         /// <param name="e"></param>
174         /// <since_tizen> 3 </since_tizen>
175         protected virtual void OnAppControlReceived(AppControlReceivedEventArgs e)
176         {
177             AppControlReceived?.Invoke(this, e);
178         }
179
180         /// <summary>
181         /// Overrides this method if want to handle behavior when the system memory is low.
182         /// If base.OnLowMemory() is not called, the event 'LowMemory' will not be emitted.
183         /// </summary>
184         /// <param name="e">The low memory event argument</param>
185         /// <since_tizen> 3 </since_tizen>
186         protected virtual void OnLowMemory(LowMemoryEventArgs e)
187         {
188             LowMemory?.Invoke(this, e);
189             double interval = new Random().Next(10 * 1000);
190             if (interval <= 0)
191                 interval = 10 * 1000;
192
193             sTimer = new Timer(interval);
194             sTimer.Elapsed += OnTimedEvent;
195             sTimer.AutoReset = false;
196             sTimer.Enabled = true;
197         }
198
199         private static void OnTimedEvent(Object source, ElapsedEventArgs e)
200         {
201             System.GC.Collect();
202         }
203
204         /// <summary>
205         /// Overrides this method if want to handle behavior when the system battery is low.
206         /// If base.OnLowBattery() is not called, the event 'LowBattery' will not be emitted.
207         /// </summary>
208         /// <param name="e">The low battery event argument</param>
209         /// <since_tizen> 3 </since_tizen>
210         protected virtual void OnLowBattery(LowBatteryEventArgs e)
211         {
212             LowBattery?.Invoke(this, e);
213         }
214
215         /// <summary>
216         /// Overrides this method if want to handle behavior when the system language is changed.
217         /// If base.OnLocaleChanged() is not called, the event 'LocaleChanged' will not be emitted.
218         /// </summary>
219         /// <param name="e">The locale changed event argument</param>
220         /// <since_tizen> 3 </since_tizen>
221         protected virtual void OnLocaleChanged(LocaleChangedEventArgs e)
222         {
223             if (!GlobalizationMode.Invariant)
224             {
225                 ChangeCurrentUICultureInfo(e.Locale);
226             }
227
228             LocaleChanged?.Invoke(this, e);
229         }
230
231         /// <summary>
232         /// Overrides this method if want to handle behavior when the region format is changed.
233         /// If base.OnRegionFormatChanged() is not called, the event 'RegionFormatChanged' will not be emitted.
234         /// </summary>
235         /// <param name="e">The region format changed event argument</param>
236         /// <since_tizen> 3 </since_tizen>
237         protected virtual void OnRegionFormatChanged(RegionFormatChangedEventArgs e)
238         {
239             if (!GlobalizationMode.Invariant)
240             {
241                 ChangeCurrentCultureInfo(e.Region);
242             }
243
244             RegionFormatChanged?.Invoke(this, e);
245         }
246
247         /// <summary>
248         /// Overrides this method if want to handle behavior when the device orientation is changed.
249         /// If base.OnRegionFormatChanged() is not called, the event 'RegionFormatChanged' will not be emitted.
250         /// </summary>
251         /// <param name="e">The device orientation changed event argument</param>
252         /// <since_tizen> 3 </since_tizen>
253         protected virtual void OnDeviceOrientationChanged(DeviceOrientationEventArgs e)
254         {
255             DeviceOrientationChanged?.Invoke(this, e);
256         }
257
258         /// <summary>
259         /// Releases any unmanaged resources used by this object. Can also dispose any other disposable objects.
260         /// </summary>
261         /// <param name="disposing">If true, disposes any disposable objects. If false, does not dispose disposable objects.</param>
262         /// <since_tizen> 3 </since_tizen>
263         protected override void Dispose(bool disposing)
264         {
265             if (!_disposedValue)
266             {
267                 if (disposing)
268                 {
269                     _backend.Dispose();
270                 }
271
272                 _disposedValue = true;
273             }
274             base.Dispose(disposing);
275         }
276
277         private CultureInfo ConvertCultureInfo(string locale)
278         {
279             ULocale pLocale = new ULocale(locale);
280             string cultureName = CultureInfoHelper.GetCultureName(pLocale.Locale.Replace("_", "-"));
281
282             if (!string.IsNullOrEmpty(cultureName))
283             {
284                 try
285                 {
286                     return new CultureInfo(cultureName);
287                 }
288                 catch (CultureNotFoundException)
289                 {
290                     Log.Error(LogTag, "CultureNotFoundException occurs. CultureName: " + cultureName);
291                 }
292             }
293
294             try
295             {
296                 return new CultureInfo(pLocale.LCID);
297             }
298             catch (ArgumentOutOfRangeException)
299             {
300                 return GetFallbackCultureInfo(pLocale);
301             }
302             catch (CultureNotFoundException)
303             {
304                 return GetFallbackCultureInfo(pLocale);
305             }
306         }
307
308         private void ChangeCurrentCultureInfo(string locale)
309         {
310             CultureInfo cultureInfo = ConvertCultureInfo(locale);
311             if (cultureInfo != null)
312             {
313                 CultureInfo.CurrentCulture = cultureInfo;
314             }
315             else
316             {
317                 Log.Error(LogTag, "CultureInfo is null. locale: " + locale);
318             }
319         }
320
321         private void ChangeCurrentUICultureInfo(string locale)
322         {
323             CultureInfo cultureInfo = ConvertCultureInfo(locale);
324             if (cultureInfo != null)
325             {
326                 CultureInfo.CurrentUICulture = cultureInfo;
327             }
328             else
329             {
330                 Log.Error(LogTag, "CultureInfo is null. locale: " + locale);
331             }
332         }
333
334         private bool ExistCultureInfo(string locale)
335         {
336             foreach (var cultureInfo in CultureInfo.GetCultures(CultureTypes.AllCultures))
337             {
338                 if (cultureInfo.Name == locale)
339                 {
340                     return true;
341                 }
342             }
343
344             return false;
345         }
346
347         private CultureInfo GetCultureInfo(string locale)
348         {
349             if (!ExistCultureInfo(locale))
350             {
351                 return null;
352             }
353
354             try
355             {
356                 return new CultureInfo(locale);
357             }
358             catch (CultureNotFoundException)
359             {
360                 return null;
361             }
362         }
363
364         private CultureInfo GetFallbackCultureInfo(ULocale uLocale)
365         {
366             CultureInfo fallbackCultureInfo = null;
367             string locale = string.Empty;
368
369             if (uLocale.Locale != null)
370             {
371                 locale = uLocale.Locale.Replace("_", "-");
372                 fallbackCultureInfo = GetCultureInfo(locale);
373             }
374
375             if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Script != null && uLocale.Country != null)
376             {
377                 locale = uLocale.Language + "-" + uLocale.Script + "-" + uLocale.Country;
378                 fallbackCultureInfo = GetCultureInfo(locale);
379             }
380
381             if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Script != null)
382             {
383                 locale = uLocale.Language + "-" + uLocale.Script;
384                 fallbackCultureInfo = GetCultureInfo(locale);
385             }
386
387             if (fallbackCultureInfo == null && uLocale.Language != null && uLocale.Country != null)
388             {
389                 locale = uLocale.Language + "-" + uLocale.Country;
390                 fallbackCultureInfo = GetCultureInfo(locale);
391             }
392
393             if (fallbackCultureInfo == null && uLocale.Language != null)
394             {
395                 locale = uLocale.Language;
396                 fallbackCultureInfo = GetCultureInfo(locale);
397             }
398
399             if (fallbackCultureInfo == null)
400             {
401                 try
402                 {
403                     fallbackCultureInfo = new CultureInfo("en");
404                 }
405                 catch (CultureNotFoundException e)
406                 {
407                     Log.Error(LogTag, "Failed to create CultureInfo. err = " + e.Message);
408                 }
409             }
410
411             return fallbackCultureInfo;
412         }
413     }
414
415     internal static class GlobalizationMode
416     {
417         private static int _invariant = -1;
418
419         internal static bool Invariant
420         {
421             get
422             {
423                 if (_invariant == -1)
424                 {
425                     string value = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT");
426                     _invariant = value != null ? (value.Equals("1") ? 1 : 0) : 0;
427                 }
428
429                 return _invariant != 0;
430             }
431         }
432     }
433
434     internal class ULocale
435     {
436         private const int ULOC_FULLNAME_CAPACITY = 157;
437         private const int ULOC_LANG_CAPACITY = 12;
438         private const int ULOC_SCRIPT_CAPACITY = 6;
439         private const int ULOC_COUNTRY_CAPACITY = 4;
440         private const int ULOC_VARIANT_CAPACITY = ULOC_FULLNAME_CAPACITY;
441
442         internal ULocale(string locale)
443         {
444             Locale = Canonicalize(locale);
445             Language = GetLanguage(Locale);
446             Script = GetScript(Locale);
447             Country = GetCountry(Locale);
448             Variant = GetVariant(Locale);
449             LCID = GetLCID(Locale);
450         }
451
452         internal string Locale { get; private set; }
453         internal string Language { get; private set; }
454         internal string Script { get; private set; }
455         internal string Country { get; private set; }
456         internal string Variant { get; private set; }
457         internal int LCID { get; private set; }
458
459         private string Canonicalize(string localeName)
460         {
461             // Get the locale name from ICU
462             StringBuilder sb = new StringBuilder(ULOC_FULLNAME_CAPACITY);
463             if (Interop.BaseUtilsi18n.Canonicalize(localeName, sb, sb.Capacity) <= 0)
464             {
465                 return null;
466             }
467
468             return sb.ToString();
469         }
470
471         private string GetLanguage(string locale)
472         {
473             // Get the language name from ICU
474             StringBuilder sb = new StringBuilder(ULOC_LANG_CAPACITY);
475             if (Interop.BaseUtilsi18n.GetLanguage(locale, sb, sb.Capacity, out int bufSizeLanguage) != 0)
476             {
477                 return null;
478             }
479
480             return sb.ToString();
481         }
482
483         private string GetScript(string locale)
484         {
485             // Get the script name from ICU
486             StringBuilder sb = new StringBuilder(ULOC_SCRIPT_CAPACITY);
487             if (Interop.BaseUtilsi18n.GetScript(locale, sb, sb.Capacity) <= 0)
488             {
489                 return null;
490             }
491
492             return sb.ToString();
493         }
494
495         private string GetCountry(string locale)
496         {
497             int err = 0;
498
499             // Get the country name from ICU
500             StringBuilder sb = new StringBuilder(ULOC_COUNTRY_CAPACITY);
501             if (Interop.BaseUtilsi18n.GetCountry(locale, sb, sb.Capacity, out err) <= 0)
502             {
503                 return null;
504             }
505
506             return sb.ToString();
507         }
508
509         private string GetVariant(string locale)
510         {
511             // Get the variant name from ICU
512             StringBuilder sb = new StringBuilder(ULOC_VARIANT_CAPACITY);
513             if (Interop.BaseUtilsi18n.GetVariant(locale, sb, sb.Capacity) <= 0)
514             {
515                 return null;
516             }
517
518             return sb.ToString();
519         }
520
521         private int GetLCID(string locale)
522         {
523             // Get the LCID from ICU
524             uint lcid = Interop.BaseUtilsi18n.GetLCID(locale);
525             return (int)lcid;
526         }
527
528         internal static string GetDefaultLocale()
529         {
530             IntPtr stringPtr = IntPtr.Zero;
531             if (Interop.BaseUtilsi18n.GetDefault(out stringPtr) != 0)
532             {
533                 return string.Empty;
534             }
535
536             if (stringPtr == IntPtr.Zero)
537             {
538                 return string.Empty;
539             }
540
541             return Marshal.PtrToStringAnsi(stringPtr);
542         }
543     }
544 }