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