[NUI.Gadget] Support Unload() method to release assembly (#6073)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Gadget / Tizen.NUI / NUIGadgetManager.cs
1 /*
2  * Copyright (c) 2023 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.Generic;
19 using System.Linq;
20 using System.IO;
21 using Tizen.Applications;
22 using System.ComponentModel;
23 using System.Runtime.InteropServices;
24 using System.Runtime.Loader;
25 using System.Reflection;
26 using System.Threading.Tasks;
27 using System.Security.AccessControl;
28
29 using SystemIO = System.IO;
30
31 namespace Tizen.NUI
32 {
33     /// <summary>
34     /// This class has the methods and events of the NUIGadgetManager.
35     /// </summary>
36     /// <since_tizen> 10 </since_tizen>
37     [EditorBrowsable(EditorBrowsableState.Never)]
38     public static class NUIGadgetManager
39     {
40         private static readonly Dictionary<string, NUIGadgetInfo> _gadgetInfos = new Dictionary<string, NUIGadgetInfo>();
41         private static readonly List<NUIGadget> _gadgets = new List<NUIGadget>();
42
43         static NUIGadgetManager()
44         {
45             IntPtr gadgetPkgIds = Interop.Libc.GetEnviornmentVariable("GADGET_PKGIDS");
46             if (gadgetPkgIds != IntPtr.Zero)
47             {
48                 string packages = Marshal.PtrToStringAnsi(gadgetPkgIds);
49                 if (string.IsNullOrEmpty(packages))
50                 {
51                     Log.Warn("There is no resource packages");
52                 }
53                 else
54                 {
55                     foreach (string packageId in packages.Split(':').ToList())
56                     {
57                         NUIGadgetInfo info = NUIGadgetInfo.CreateNUIGadgetInfo(packageId);
58                         if (info != null)
59                         {
60                             _gadgetInfos.Add(info.ResourceType, info);
61                         }
62                     }
63                 }
64             }
65             else
66             {
67                 Log.Warn("Failed to get environment variable");
68             }
69
70             var context = (CoreApplication)CoreApplication.Current;
71             context.AppControlReceived += OnAppControlReceived;
72             context.LowMemory += OnLowMemory;
73             context.LowBattery += OnLowBattery;
74             context.LocaleChanged += OnLocaleChanged;
75             context.RegionFormatChanged += OnRegionFormatChanged;
76             context.DeviceOrientationChanged += OnDeviceOrientationChanged;
77         }
78
79         private static void OnAppControlReceived(object sender, AppControlReceivedEventArgs args)
80         {
81             HandleAppControl(args);
82         }
83
84         private static void OnLowMemory(object sender, LowMemoryEventArgs args)
85         {
86             HandleEvents(NUIGadgetEventType.LowMemory, args);
87         }
88
89         private static void OnLowBattery(object sender, LowBatteryEventArgs args)
90         {
91             HandleEvents(NUIGadgetEventType.LowBattery, args);
92         }
93
94         private static void OnLocaleChanged(object sender, LocaleChangedEventArgs args)
95         {
96             HandleEvents(NUIGadgetEventType.LocaleChanged, args);
97         }
98
99         private static void OnRegionFormatChanged(object sender, RegionFormatChangedEventArgs args)
100         {
101             HandleEvents(NUIGadgetEventType.RegionFormatChanged, args);
102         }
103
104         private static void OnDeviceOrientationChanged(object sender, DeviceOrientationEventArgs args)
105         {
106             HandleEvents(NUIGadgetEventType.DeviceORientationChanged, args);
107         }
108
109         /// <summary>
110         /// Occurs when the lifecycle of the NUIGadget is changed.
111         /// </summary>
112         /// <since_tizen> 10 </since_tizen>
113         public static event EventHandler<NUIGadgetLifecycleChangedEventArgs> NUIGadgetLifecycleChanged;
114
115         private static void OnNUIGadgetLifecycleChanged(object sender, NUIGadgetLifecycleChangedEventArgs args)
116         {
117             NUIGadgetLifecycleChanged?.Invoke(sender, args);
118
119             if (args.State == NUIGadgetLifecycleState.Destroyed)
120             {
121                 args.Gadget.LifecycleChanged -= OnNUIGadgetLifecycleChanged;
122                 _gadgets.Remove(args.Gadget);
123             }
124         }
125
126         private static NUIGadgetInfo Find(string resourceType)
127         {
128             if (!_gadgetInfos.TryGetValue(resourceType, out NUIGadgetInfo info))
129             {
130                 throw new ArgumentException("Failed to find NUIGadgetInfo. resource type: " + resourceType);
131             }
132
133             return info;
134         }
135
136         /// <summary>
137         /// Loads an assembly of the NUIGadget.
138         /// </summary>
139         /// <param name="resourceType">The resource type of the NUIGadget package.</param>
140         /// <exception cref="ArgumentException">Thrown when failed because of a invalid argument.</exception>
141         /// <exception cref="InvalidOperationException">Thrown when failed because of an invalid operation.</exception>
142         /// <since_tizen> 10 </since_tizen>
143         public static void Load(string resourceType)
144         {
145             Load(resourceType, true);
146         }
147
148         /// <summary>
149         /// Loads an assembly of the NUIGadget.
150         /// </summary>
151         /// <param name="resourceType">The resource type of the NUIGadget package.</param>
152         /// <param name="useDefaultContext">The flag if ture, use a default load context. Otherwise, use a new load context.</param>
153         /// <exception cref="ArgumentException">Thrown when failed because of a invalid argument.</exception>
154         /// <exception cref="InvalidOperationException">Thrown when failed because of an invalid operation.</exception>
155         /// <since_tizen> 10 </since_tizen>
156         public static void Load(string resourceType, bool useDefaultContext)
157         {
158             if (string.IsNullOrEmpty(resourceType))
159             {
160                 throw new ArgumentException("Invalid argument");
161             }
162
163             NUIGadgetInfo info = Find(resourceType);
164             Load(info, useDefaultContext);
165         }
166
167         /// <summary>
168         /// Unloads the loaded assembly of the NUIGadget.
169         /// </summary>
170         /// <param name="resourceType">The resource type of the NUIGadget package.</param>
171         /// <exception cref="ArgumentException">Thrown when failed because of a invalid argument.</exception>
172         /// <since_tizen> 10 </since_tizen>
173         public static void Unload(string resourceType)
174         {
175             if (string.IsNullOrEmpty(resourceType))
176             {
177                 throw new ArgumentException("Invalid argument");
178             }
179
180             NUIGadgetInfo info = Find(resourceType);
181             Unload(info);
182         }
183
184         private static void Unload(NUIGadgetInfo info)
185         {
186             if (info == null)
187             {
188                 throw new ArgumentException("Invalid argument");
189             }
190
191             lock (info)
192             {
193                 if (info.NUIGadgetAssembly != null && info.NUIGadgetAssembly.IsLoaded)
194                 {
195                     info.NUIGadgetAssembly.Unload();
196                     info.NUIGadgetAssembly = null;
197                 }
198             }
199         }
200
201         private static void Load(NUIGadgetInfo info, bool useDefaultContext)
202         {
203             if (info == null)
204             {
205                 throw new ArgumentException("Invalid argument");
206             }
207
208             try
209             {
210                 lock (info)
211                 {
212                     if (useDefaultContext)
213                     {
214                         if (info.Assembly == null)
215                         {
216
217                             Log.Warn("NUIGadget.Load(): " + info.ResourcePath + info.ExecutableFile + " ++");
218                             info.Assembly = Assembly.Load(SystemIO.Path.GetFileNameWithoutExtension(info.ExecutableFile));
219                             Log.Warn("NUIGadget.Load(): " + info.ResourcePath + info.ExecutableFile + " --");
220                         }
221                     }
222                     else
223                     {
224                         if (info.NUIGadgetAssembly == null)
225                         {
226                             Log.Warn("NUIGadgetAssembly.Load(): " + info.ResourcePath + info.ExecutableFile + " ++");
227                             info.NUIGadgetAssembly = new NUIGadgetAssembly(info.ResourcePath + info.ExecutableFile);
228                             info.NUIGadgetAssembly.Load();
229                             Log.Warn("NUIGadgetAssembly.Load(): " + info.ResourcePath + info.ExecutableFile + " --");
230                         }
231                     }
232                 }
233             }
234             catch (FileLoadException e)
235             {
236                 throw new InvalidOperationException(e.Message);
237             }
238             catch (BadImageFormatException e)
239             {
240                 throw new InvalidOperationException(e.Message);
241             }
242         }
243
244         /// <summary>
245         /// Adds a NUIGadget to the NUIGadgetManager.
246         /// </summary>
247         /// <param name="resourceType">The resource type of the NUIGadget package.</param>
248         /// <param name="className">The class name of the NUIGadget.</param>
249         /// <returns>The NUIGadget object.</returns>
250         /// <exception cref="ArgumentException">Thrown when failed because of a invalid argument.</exception>
251         /// <exception cref="InvalidOperationException">Thrown when failed because of an invalid operation.</exception>
252         /// <since_tizen> 10 </since_tizen>
253         public static NUIGadget Add(string resourceType, string className)
254         {
255             return Add(resourceType, className, true);
256         }
257
258         /// <summary>
259         /// Adds a NUIGadget to the NUIGadgetManager.
260         /// </summary>
261         /// <param name="resourceType">The resource type of the NUIGadget package.</param>
262         /// <param name="className">The class name of the NUIGadget.</param>
263         /// <param name="useDefaultContext">The flag it true, use a default context. Otherwise, use a new load context.</param>
264         /// <returns>The NUIGadget object.</returns>
265         /// <exception cref="ArgumentException">Thrown when failed because of a invalid argument.</exception>
266         /// <exception cref="InvalidOperationException">Thrown when failed because of an invalid operation.</exception>
267         /// <since_tizen> 10 </since_tizen>
268         public static NUIGadget Add(string resourceType, string className, bool useDefaultContext)
269         {
270             if (string.IsNullOrEmpty(resourceType) || string.IsNullOrEmpty(className))
271             {
272                 throw new ArgumentException("Invalid argument");
273             }
274
275             NUIGadgetInfo info = Find(resourceType);
276             Load(info, useDefaultContext);
277
278             NUIGadget gadget = useDefaultContext ? info.Assembly.CreateInstance(className, true) as NUIGadget : info.NUIGadgetAssembly.CreateInstance(className);
279             if (gadget == null)
280             {
281                 throw new InvalidOperationException("Failed to create instance. className: " + className);
282             }
283
284             gadget.NUIGadgetInfo = info;
285             gadget.ClassName = className;
286             gadget.NUIGadgetResourceManager = new NUIGadgetResourceManager(info);
287             gadget.LifecycleChanged += OnNUIGadgetLifecycleChanged;
288             if (!gadget.Create())
289             {
290                 throw new InvalidOperationException("The View MUST be created");
291             }
292
293             _gadgets.Add(gadget);
294             return gadget;
295         }
296
297         /// <summary>
298         /// Gets the instance of the running NUIGadgets.
299         /// </summary>
300         /// <returns>The NUIGadget list.</returns>
301         /// <since_tizen> 10 </since_tizen>
302         public static IEnumerable<NUIGadget> GetGadgets()
303         {
304             return _gadgets;
305         }
306
307         /// <summary>
308         /// Gets the information of the available NUIGadgets.
309         /// </summary>
310         /// <remarks>
311         /// This method only returns the available gadget informations, not all installed gadget informations.
312         /// The resource package of the NUIGadget can set the allowed packages using "allowed-package".
313         /// When executing an application, the platform mounts the resource package into the resource path of the application.
314         /// </remarks>
315         /// <returns>The NUIGadgetInfo list.</returns>
316         /// <since_tizen> 10 </since_tizen>
317         public static IEnumerable<NUIGadgetInfo> GetGadgetInfos()
318         {
319             return _gadgetInfos.Values.ToList();
320         }
321
322         /// <summary>
323         /// Removes the NUIGadget from the NUIGadgetManager.
324         /// </summary>
325         /// <param name="gadget">The NUIGadget object.</param>
326         /// <since_tizen> 10 </since_tizen>
327         public static void Remove(NUIGadget gadget)
328         {
329             if (gadget == null || !_gadgets.Contains(gadget))
330             {
331                 return;
332             }
333
334             if (gadget.State == NUIGadgetLifecycleState.Destroyed)
335             {
336                 return;
337             }
338
339             _gadgets.Remove(gadget);
340             CoreApplication.Post(() => {
341                 Log.Warn("ResourceType: " + gadget.NUIGadgetInfo.ResourceType + ", State: " + gadget.State);
342                 gadget.Finish();
343             });
344         }
345
346         /// <summary>
347         /// Removes all NUIGadgets from the NUIGadgetManager.
348         /// </summary>
349         /// <since_tizen> 10 </since_tizen>
350         public static void RemoveAll()
351         {
352             for (int i = _gadgets.Count - 1;  i >= 0; i--)
353             {
354                 Remove(_gadgets[i]);
355             }
356         }
357
358         /// <summary>
359         /// Resumes the running NUIGadget.
360         /// </summary>
361         /// <param name="gadget">The NUIGadget object.</param>
362         /// <since_tizen> 10 </since_tizen>
363         public static void Resume(NUIGadget gadget)
364         {
365             if (!_gadgets.Contains(gadget))
366             {
367                 return;
368             }
369
370             CoreApplication.Post(() =>
371             {
372                 Log.Warn("ResourceType: " + gadget.NUIGadgetInfo.ResourceType + ", State: " + gadget.State);
373                 gadget.Resume();
374             });
375         }
376
377         /// <summary>
378         /// Pauses the running NUIGadget.
379         /// </summary>
380         /// <param name="gadget">The NUIGadget object.</param>
381         /// <since_tizen> 10 </since_tizen>
382         public static void Pause(NUIGadget gadget)
383         {
384             if (!_gadgets.Contains(gadget))
385             {
386                 return;
387             }
388
389             CoreApplication.Post(() =>
390             {
391                 Log.Warn("ResourceType: " + gadget.NUIGadgetInfo.ResourceType + ", State: " + gadget.State);
392                 gadget.Pause();
393             });
394         }
395
396         /// <summary>
397         /// Sends the appcontrol to the running NUIGadget.
398         /// </summary>
399         /// <param name="gadget">The NUIGadget object.</param>
400         /// <param name="appControl">The appcontrol object.</param>
401         /// <exception cref="ArgumentException">Thrown when failed because of a invalid argument.</exception>
402         /// <exception cref="ArgumentNullException">Thrown when failed because the argument is null.</exception>
403         /// <since_tizen> 10 </since_tizen>
404         public static void SendAppControl(NUIGadget gadget, AppControl appControl)
405         {
406             if (gadget == null)
407             {
408                 throw new ArgumentNullException(nameof(gadget));
409             }
410
411             if (!_gadgets.Contains(gadget))
412             {
413                 throw new ArgumentException("Invalid argument");
414             }
415
416             if (appControl == null)
417             {
418                 throw new ArgumentNullException(nameof(appControl));
419             }
420
421             gadget.HandleAppControlReceivedEvent(new AppControlReceivedEventArgs(new ReceivedAppControl(appControl.SafeAppControlHandle)));
422         }
423
424         internal static bool HandleAppControl(AppControlReceivedEventArgs args)
425         {
426             var extraData = args.ReceivedAppControl?.ExtraData;
427             if (extraData == null||!extraData.TryGet("__K_GADGET_RES_TYPE", out string resourceType) ||
428                 !extraData.TryGet("__K_GADGET_CLASS_NAME", out string className))
429             {
430                 return false;
431             }
432
433             foreach (NUIGadget gadget in _gadgets)
434             {
435                 if (gadget.NUIGadgetInfo.ResourceType == resourceType && gadget.ClassName == className)
436                 {
437                     gadget.HandleAppControlReceivedEvent(args);
438                     return true;
439                 }
440             }
441
442             return false;
443         }
444
445         internal static void HandleEvents(NUIGadgetEventType eventType, EventArgs args)
446         {
447             foreach (NUIGadget gadget in _gadgets)
448             {
449                 gadget.HandleEvents(eventType, args);
450             }
451         }
452     }
453 }