Release 4.0.0-preview1-00051
[platform/core/csapi/tizenfx.git] / src / Tizen.Applications.Common / Tizen.Applications / AppControl.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.Generic;
19 using System.Linq;
20 using System.Runtime.InteropServices;
21
22 namespace Tizen.Applications
23 {
24     /// <summary>
25     /// Represents the control message to exchange between applications.
26     /// </summary>
27     /// <example>
28     /// <code>
29     /// public class AppControlExample : UIApplication
30     /// {
31     ///     /// ...
32     ///     protected override void OnAppControlReceived(AppControlReceivedEventArgs e)
33     ///     {
34     ///         AppControl appControl = new AppControl();
35     ///         appControl.ApplicationId = "org.tizen.calculator";
36     ///         AppControl.SendLaunchRequest(appControl, (launchRequest, replyRequest, result) => {
37     ///             // ...
38     ///         });
39     ///     }
40     /// }
41     /// </code>
42     /// </example>
43     public class AppControl
44     {
45         private const string LogTag = "Tizen.Applications";
46
47         private static Dictionary<int, Interop.AppControl.ReplyCallback> s_replyNativeCallbackMaps = new Dictionary<int, Interop.AppControl.ReplyCallback>();
48         private static int s_replyNativeCallbackId = 0;
49
50         private readonly SafeAppControlHandle _handle;
51
52         private string _operation = null;
53         private string _mime = null;
54         private string _uri = null;
55         private string _category = null;
56         private string _applicationId = null;
57         private ExtraDataCollection _extraData = null;
58
59         /// <summary>
60         /// Initializes the instance of the AppControl class.
61         /// </summary>
62         /// <exception cref="InvalidOperationException">Thrown when failed to create AppControl handle.</exception>
63         public AppControl()
64         {
65             Interop.AppControl.ErrorCode err = Interop.AppControl.Create(out _handle);
66             if (err != Interop.AppControl.ErrorCode.None)
67             {
68                 throw new InvalidOperationException("Failed to create the appcontrol handle. Err = " + err);
69             }
70         }
71
72         /// <summary>
73         /// Initializes the instance of the AppControl class with parameter.
74         /// </summary>
75         /// <param name="enableAppStartedResultEvent">The flag value to receive an additional launch result event on launch request.</param>
76         /// <exception cref="InvalidOperationException">Thrown when failed to create AppControl handle.</exception>
77         public AppControl(bool enableAppStartedResultEvent)
78         {
79             Interop.AppControl.ErrorCode err = Interop.AppControl.Create(out _handle);
80             if (err != Interop.AppControl.ErrorCode.None)
81             {
82                 throw new InvalidOperationException("Failed to create the appcontrol handle. Err = " + err);
83             }
84
85             if (enableAppStartedResultEvent)
86             {
87                 err = Interop.AppControl.EnableAppStartedResultEvent(_handle);
88                 if (err != Interop.AppControl.ErrorCode.None)
89                 {
90                     throw new InvalidOperationException("Failed to set EnableAppStartedResultEvent");
91                 }
92             }
93         }
94
95         /// <summary>
96         /// Initializes the instance of the AppControl class with the SafeAppControlHandle.
97         /// </summary>
98         /// <param name="handle"></param>
99         public AppControl(SafeAppControlHandle handle)
100         {
101             if (handle == null)
102             {
103                 throw new ArgumentNullException("handle");
104             }
105
106             Interop.AppControl.ErrorCode err = Interop.AppControl.DangerousClone(out _handle, handle.DangerousGetHandle());
107             if (err != Interop.AppControl.ErrorCode.None)
108             {
109                 throw new InvalidOperationException("Failed to clone the appcontrol handle. Err = " + err);
110             }
111         }
112
113         private AppControl(IntPtr handle)
114         {
115             Interop.AppControl.ErrorCode err = Interop.AppControl.DangerousClone(out _handle, handle);
116             if (err != Interop.AppControl.ErrorCode.None)
117             {
118                 throw new InvalidOperationException("Failed to clone the appcontrol handle. Err = " + err);
119             }
120         }
121
122         #region Public Properties
123
124         /// <summary>
125         /// Gets the SafeAppControlHandle instance.
126         /// </summary>
127         public SafeAppControlHandle SafeAppControlHandle
128         {
129             get
130             {
131                 return _handle;
132             }
133         }
134
135         /// <summary>
136         /// Gets and sets the operation to be performed.
137         /// </summary>
138         /// <value>
139         /// The operation is the mandatory information for the launch request. If the operation is not specified,
140         /// AppControlOperations.Default is used for the launch request. If the operation is AppControlOperations.Default,
141         /// the package information is mandatory to explicitly launch the application.
142         /// (if the operation is null for setter, it clears the previous value.)
143         /// </value>
144         /// <example>
145         /// <code>
146         /// AppControl appControl = new AppControl();
147         /// appControl.Operation = AppControlOperations.Default;
148         /// Log.Debug(LogTag, "Operation: " + appControl.Operation);
149         /// </code>
150         /// </example>
151         public string Operation
152         {
153             get
154             {
155                 if (String.IsNullOrEmpty(_operation))
156                 {
157                     Interop.AppControl.ErrorCode err = Interop.AppControl.GetOperation(_handle, out _operation);
158                     if (err != Interop.AppControl.ErrorCode.None)
159                     {
160                         Log.Warn(LogTag, "Failed to get the operation from the appcontrol. Err = " + err);
161                     }
162                 }
163                 return _operation;
164             }
165             set
166             {
167                 Interop.AppControl.ErrorCode err = Interop.AppControl.SetOperation(_handle, value);
168                 if (err == Interop.AppControl.ErrorCode.None)
169                 {
170                     _operation = value;
171                 }
172                 else
173                 {
174                     Log.Warn(LogTag, "Failed to set the operation to the appcontrol. Err = " + err);
175                 }
176             }
177         }
178
179         /// <summary>
180         /// Gets and sets the explicit MIME type of the data.
181         /// </summary>
182         /// <value>
183         /// (if the mime is null for setter, it clears the previous value.)
184         /// </value>
185         /// <example>
186         /// <code>
187         /// AppControl appControl = new AppControl();
188         /// appControl.Mime = "image/jpg";
189         /// Log.Debug(LogTag, "Mime: " + appControl.Mime);
190         /// </code>
191         /// </example>
192         public string Mime
193         {
194             get
195             {
196                 if (String.IsNullOrEmpty(_mime))
197                 {
198                     Interop.AppControl.ErrorCode err = Interop.AppControl.GetMime(_handle, out _mime);
199                     if (err != Interop.AppControl.ErrorCode.None)
200                     {
201                         Log.Warn(LogTag, "Failed to get the mime from the appcontrol. Err = " + err);
202                     }
203                 }
204                 return _mime;
205             }
206             set
207             {
208                 Interop.AppControl.ErrorCode err = Interop.AppControl.SetMime(_handle, value);
209                 if (err == Interop.AppControl.ErrorCode.None)
210                 {
211                     _mime = value;
212                 }
213                 else
214                 {
215                     Log.Warn(LogTag, "Failed to set the mime to the appcontrol. Err = " + err);
216                 }
217             }
218         }
219
220         /// <summary>
221         /// Gets and sets the URI of the data.
222         /// </summary>
223         /// <value>
224         /// Since Tizen 2.4, if the parameter 'uri' is started with 'file://' and
225         /// it is a regular file in this application's data path which can be obtained
226         /// by property DataPath in ApplicationInfo class,
227         /// it will be shared to the callee application.
228         /// Framework will grant a temporary permission to the callee application for this file and
229         /// revoke it when the callee application is terminated.
230         /// The callee application can just read it.
231         /// (if the uri is null for setter, it clears the previous value.)
232         /// </value>
233         /// <example>
234         /// <code>
235         /// public class AppControlExample : UIApplication
236         /// {
237         ///     ...
238         ///     protected override void OnAppControlReceived(AppControlReceivedEventArgs e)
239         ///     {
240         ///         ...
241         ///         AppControl appControl = new AppControl();
242         ///         appContrl.Uri = this.ApplicationInfo.DataPath + "image.jpg";
243         ///         Log.Debug(LogTag, "Set Uri: " + appControl.Uri);
244         ///     }
245         /// }
246         /// </code>
247         /// </example>
248         public string Uri
249         {
250             get
251             {
252                 if (String.IsNullOrEmpty(_uri))
253                 {
254                     Interop.AppControl.ErrorCode err = Interop.AppControl.GetUri(_handle, out _uri);
255                     if (err != Interop.AppControl.ErrorCode.None)
256                     {
257                         Log.Warn(LogTag, "Failed to get the uri from the appcontrol. Err = " + err);
258                     }
259                 }
260                 return _uri;
261             }
262             set
263             {
264                 Interop.AppControl.ErrorCode err = Interop.AppControl.SetUri(_handle, value);
265                 if (err == Interop.AppControl.ErrorCode.None)
266                 {
267                     _uri = value;
268                 }
269                 else
270                 {
271                     Log.Warn(LogTag, "Failed to set the uri to the appcontrol. Err = " + err);
272                 }
273             }
274         }
275
276         /// <summary>
277         /// Gets and sets the explicit category.
278         /// </summary>
279         /// <value>
280         /// (if the category is null for setter, it clears the previous value.)
281         /// </value>
282         public string Category
283         {
284             get
285             {
286                 if (String.IsNullOrEmpty(_category))
287                 {
288                     Interop.AppControl.ErrorCode err = Interop.AppControl.GetCategory(_handle, out _category);
289                     if (err != Interop.AppControl.ErrorCode.None)
290                     {
291                         Log.Warn(LogTag, "Failed to get the category from the appcontrol. Err = " + err);
292                     }
293                 }
294                 return _category;
295             }
296             set
297             {
298                 Interop.AppControl.ErrorCode err = Interop.AppControl.SetCategory(_handle, value);
299                 if (err == Interop.AppControl.ErrorCode.None)
300                 {
301                     _category = value;
302                 }
303                 else
304                 {
305                     Log.Warn(LogTag, "Failed to set the category to the appcontrol. Err = " + err);
306                 }
307             }
308         }
309
310         /// <summary>
311         /// Gets and sets the application id to explicitly launch.
312         /// </summary>
313         /// <value>
314         /// (if the application id is null for setter, it clears the previous value.)
315         /// </value>
316         /// <example>
317         /// <code>
318         /// AppControl appControl = new AppControl();
319         /// appControl.ApplicationId = "org.tizen.calculator";
320         /// Log.Debug(LogTag, "ApplicationId: " + appControl.ApplicationId);
321         /// </code>
322         /// </example>
323         public string ApplicationId
324         {
325             get
326             {
327                 if (String.IsNullOrEmpty(_applicationId))
328                 {
329                     Interop.AppControl.ErrorCode err = Interop.AppControl.GetAppId(_handle, out _applicationId);
330                     if (err != Interop.AppControl.ErrorCode.None)
331                     {
332                         Log.Warn(LogTag, "Failed to get the application id from the AppControl. Err = " + err);
333                     }
334                 }
335                 return _applicationId;
336             }
337             set
338             {
339                 Interop.AppControl.ErrorCode err = Interop.AppControl.SetAppId(_handle, value);
340                 if (err == Interop.AppControl.ErrorCode.None)
341                 {
342                     _applicationId = value;
343                 }
344                 else
345                 {
346                     Log.Warn(LogTag, "Failed to set the application id to the AppControl. Err = " + err);
347                 }
348             }
349         }
350
351         /// <summary>
352         /// Gets and sets the launch mode of the application.
353         /// </summary>
354         /// <value>
355         /// Although LaunchMode were set as AppControlLaunchMode.Group,
356         /// callee application would be launched as single mode
357         /// if the manifest file of callee application defined the launch mode as "single".
358         /// This property can just set the preference of caller application to launch an application.
359         /// Sub-applications which were launched as group mode always have own process.
360         /// Since Tizen 3.0, if launch mode not set in the caller app control,
361         /// this property returns AppControlLaunchMode.Single launch mode.
362         /// </value>
363         /// <example>
364         /// <code>
365         /// AppControl appControl = new AppControl();
366         /// appControl.LaunchMode = AppControlLaunchMode.Group;
367         /// </code>
368         /// </example>
369         public AppControlLaunchMode LaunchMode
370         {
371             get
372             {
373                 int value = 0;
374                 Interop.AppControl.ErrorCode err = Interop.AppControl.GetLaunchMode(_handle, out value);
375                 if (err != Interop.AppControl.ErrorCode.None)
376                 {
377                     Log.Warn(LogTag, "Failed to get the LaunchMode from the AppControl. Err = " + err);
378                 }
379                 return (AppControlLaunchMode)value;
380             }
381             set
382             {
383                 Interop.AppControl.ErrorCode err = Interop.AppControl.SetLaunchMode(_handle, (int)value);
384                 if (err != Interop.AppControl.ErrorCode.None)
385                 {
386                     Log.Warn(LogTag, "Failed to set the LaunchMode to the AppControl. Err = " + err);
387                 }
388             }
389         }
390
391         /// <summary>
392         /// Gets the collection of the extra data.
393         /// </summary>
394         /// <value>
395         /// Extra data for communication between AppControls.
396         /// </value>
397         /// <example>
398         /// <code>
399         /// AppControl appControl = new AppControl();
400         /// appControl.ExtraData.Add("key", "value");
401         /// ...
402         /// </code>
403         /// </example>
404         public ExtraDataCollection ExtraData
405         {
406             get
407             {
408                 if (_extraData == null)
409                     _extraData = new ExtraDataCollection(_handle);
410                 return _extraData;
411             }
412         }
413
414         #endregion // Public Properties
415
416         /// <summary>
417         /// Retrieves all applications that can be launched to handle the given app_control request.
418         /// </summary>
419         /// <param name="control">The AppControl</param>
420         /// <returns>ApplicationIds</returns>
421         /// <exception cref="InvalidOperationException">Thrown when failed because of invalid parameter</exception>
422         /// <example>
423         /// <code>
424         /// IEnumerable<string> applicationIds = AppControl.GetMatchedApplicationIds(control);
425         /// if (applicationIds != null)
426         /// {
427         ///     foreach (string id in applicationIds)
428         ///     {
429         ///         // ...
430         ///     }
431         /// }
432         /// </code>
433         /// </example>
434         public static IEnumerable<string> GetMatchedApplicationIds(AppControl control)
435         {
436             if (control == null)
437             {
438                 throw new ArgumentNullException("control");
439             }
440
441             List<string> ids = new List<string>();
442             Interop.AppControl.AppMatchedCallback callback = (handle, applicationId, userData) =>
443             {
444                 if (applicationId == null)
445                 {
446                         return false;
447                 }
448
449                 ids.Add(applicationId);
450                 return true;
451             };
452
453             Interop.AppControl.ErrorCode err = Interop.AppControl.ForeachAppMatched(control._handle, callback, IntPtr.Zero);
454             if (err != Interop.AppControl.ErrorCode.None)
455             {
456                     throw new InvalidOperationException("Failed to get matched application ids. err = " + err);
457             }
458
459             return ids;
460         }
461
462         /// <summary>
463         /// Sends the launch request.
464         /// </summary>
465         /// <remarks>
466         /// The operation is mandatory information for the launch request.
467         /// If the operation is not specified, AppControlOperations.Default is used by default.
468         /// If the operation is AppControlOperations.Default, the application ID is mandatory to explicitly launch the application. \n
469         /// Since Tizen 2.4, the launch request of the service application over out of packages is restricted by the platform.
470         /// Also, implicit launch requests are NOT delivered to service applications since 2.4.
471         /// To launch a service application, an explicit launch request with application ID given by property ApplicationId MUST be sent.
472         /// </remarks>
473         /// <param name="launchRequest">The AppControl</param>
474         /// <exception cref="ArgumentNullException">Thrown when failed because of a null arguament</exception>
475         /// <exception cref="InvalidOperationException">Thrown when failed because of invalid operation</exception>
476         /// <exception cref="TimeoutException">Thrown when failed because of timeout</exception>
477         /// <privilege>http://tizen.org/privilege/appmanager.launch</privilege>
478         /// <example>
479         /// <code>
480         /// AppControl appControl = new AppControl();
481         /// appControl.ApplicationId = "org.tizen.calculator";
482         /// AppControl.SendLaunchRequest(appControl);
483         /// </code>
484         /// </example>
485         public static void SendLaunchRequest(AppControl launchRequest)
486         {
487             SendLaunchRequest(launchRequest, null);
488         }
489
490         /// <summary>
491         /// Sends the launch request.
492         /// </summary>
493         /// <remarks>
494         /// The operation is mandatory information for the launch request.
495         /// If the operation is not specified, AppControlOperations.Default is used by default.
496         /// If the operation is AppControlOperations.Default, the application ID is mandatory to explicitly launch the application. \n
497         /// Since Tizen 2.4, the launch request of the service application over out of packages is restricted by the platform.
498         /// Also, implicit launch requests are NOT delivered to service applications since 2.4.
499         /// To launch a service application, an explicit launch request with application ID given by property ApplicationId MUST be sent.
500         /// </remarks>
501         /// <param name="launchRequest">The AppControl</param>
502         /// <param name="replyAfterLaunching">The callback function to be called when the reply is delivered</param>
503         /// <exception cref="ArgumentException">Thrown when failed because of arguament is invalid</exception>
504         /// <exception cref="InvalidOperationException">Thrown when failed because of invalid operation</exception>
505         /// <exception cref="TimeoutException">Thrown when failed because of timeout</exception>
506         /// <privilege>http://tizen.org/privilege/appmanager.launch</privilege>
507         /// <example>
508         /// <code>
509         /// AppControl appControl = new AppControl();
510         /// appControl.ApplicationId = "org.tizen.calculator";
511         /// AppControl.SendLaunchRequest(appControl, (launchRequest, replyRequest, result) => {
512         ///     // ...
513         /// });
514         /// </code>
515         /// </example>
516         public static void SendLaunchRequest(AppControl launchRequest, AppControlReplyCallback replyAfterLaunching)
517         {
518             if (launchRequest == null)
519             {
520                 throw new ArgumentNullException("launchRequest");
521             }
522
523             Interop.AppControl.ErrorCode err;
524
525             if (replyAfterLaunching != null)
526             {
527                 int id = 0;
528                 lock (s_replyNativeCallbackMaps)
529                 {
530                     id = s_replyNativeCallbackId++;
531                     s_replyNativeCallbackMaps[id] = (launchRequestHandle, replyRequestHandle, result, userData) =>
532                     {
533                         if (replyAfterLaunching != null)
534                         {
535                             Log.Debug(LogTag, "Reply Callback is launched");
536                             replyAfterLaunching(new AppControl(launchRequestHandle), new AppControl(replyRequestHandle), (AppControlReplyResult)result);
537                             if (result != Interop.AppControl.AppStartedStatus)
538                             {
539                                 lock (s_replyNativeCallbackMaps)
540                                 {
541                                     s_replyNativeCallbackMaps.Remove(id);
542                                 }
543                             }
544                         }
545                     };
546                 }
547                 err = Interop.AppControl.SendLaunchRequest(launchRequest._handle, s_replyNativeCallbackMaps[id], IntPtr.Zero);
548             }
549             else
550             {
551                 err = Interop.AppControl.SendLaunchRequest(launchRequest._handle, null, IntPtr.Zero);
552             }
553
554             if (err != Interop.AppControl.ErrorCode.None)
555             {
556                 switch (err)
557                 {
558                     case Interop.AppControl.ErrorCode.InvalidParameter:
559                         throw new ArgumentException("Invalid Arguments");
560                     case Interop.AppControl.ErrorCode.TimedOut:
561                         throw new TimeoutException("Timed out");
562                     default:
563                         throw new InvalidOperationException("Error = " + err);
564                 }
565             }
566         }
567
568         /// <summary>
569         /// Sends the terminate request to the application that is launched by AppControl.
570         /// </summary>
571         /// <remarks>
572         /// You are not allowed to terminate other general applications using this API.
573         /// This API can be used to terminate sub-applications which were launched as group mode by caller application.
574         /// Once callee application is being terminated by this API,
575         /// other applications which were launched by callee application as group mode will be terminated as well
576         /// </remarks>
577         /// <param name="terminateRequest">The AppControl</param>
578         /// <exception cref="ArgumentException">Thrown when failed because of arguament is invalid</exception>
579         /// <exception cref="InvalidOperationException">Thrown when failed because of invalid operation</exception>
580         /// <exception cref="TimeoutException">Thrown when failed because of timeout</exception>
581         /// <example>
582         /// <code>
583         /// AppControl terminateRequest = new AppControl();
584         /// terminateRequest.ApplicationId = "org.tizen.calculator";
585         /// AppControl.SendTerminateRequest(terminateRequest);
586         /// </code>
587         /// </example>
588         public static void SendTerminateRequest(AppControl terminateRequest)
589         {
590             if (terminateRequest == null)
591             {
592                 throw new ArgumentNullException("terminateRequest");
593             }
594             Interop.AppControl.ErrorCode err;
595
596             err = Interop.AppControl.SendTerminateRequest(terminateRequest._handle);
597
598             if (err != Interop.AppControl.ErrorCode.None)
599             {
600                 switch (err)
601                 {
602                     case Interop.AppControl.ErrorCode.InvalidParameter:
603                         throw new ArgumentException("Invalid Arguments");
604                     case Interop.AppControl.ErrorCode.TimedOut:
605                         throw new TimeoutException("Timed out");
606                     default:
607                         throw new InvalidOperationException("Error = " + err);
608                 }
609             }
610         }
611
612         /// <summary>
613         /// Class for Extra Data
614         /// </summary>
615         public class ExtraDataCollection
616         {
617             private readonly SafeAppControlHandle _handle;
618
619             internal ExtraDataCollection(SafeAppControlHandle handle)
620             {
621                 _handle = handle;
622             }
623
624             /// <summary>
625             /// Adds extra data.
626             /// </summary>
627             /// <remarks>
628             /// The function replaces any existing value for the given key.
629             /// </remarks>
630             /// <param name="key">The name of the extra data</param>
631             /// <param name="value">The value associated with the given key</param>
632             /// <exception cref="ArgumentNullException">Thrown when key or value is a zero-length string</exception>
633             /// <exception cref="ArgumentException">Thrown when the application tries to use the same key with system-defined key</exception>
634             /// <example>
635             /// <code>
636             /// AppControl appControl = new AppControl();
637             /// appControl.ExtraData.Add("myKey", "myValue");
638             /// </code>
639             /// </example>
640             public void Add(string key, string value)
641             {
642                 if (string.IsNullOrEmpty(key))
643                 {
644                     throw new ArgumentNullException("key");
645                 }
646                 if (string.IsNullOrEmpty(value))
647                 {
648                     throw new ArgumentNullException("value");
649                 }
650                 Interop.AppControl.ErrorCode err = Interop.AppControl.AddExtraData(_handle, key, value);
651                 if (err != Interop.AppControl.ErrorCode.None)
652                 {
653                     switch (err)
654                     {
655                         case Interop.AppControl.ErrorCode.InvalidParameter:
656                             throw new ArgumentException("Invalid parameter: key or value is a zero-length string");
657                         case Interop.AppControl.ErrorCode.KeyRejected:
658                             throw new ArgumentException("Key is rejected: the key is system-defined key.");
659                         default:
660                             throw new InvalidOperationException("Error = " + err);
661                     }
662                 }
663             }
664
665             /// <summary>
666             /// Adds extra data.
667             /// </summary>
668             /// <remarks>
669             /// The function replaces any existing value for the given key.
670             /// </remarks>
671             /// <param name="key">The name of the extra data</param>
672             /// <param name="value">The value associated with the given key</param>
673             /// <exception cref="ArgumentNullException">Thrown when key or value is a zero-length string</exception>
674             /// <exception cref="ArgumentException">Thrown when the application tries to use the same key with system-defined key</exception>
675             /// <example>
676             /// <code>
677             /// AppControl appControl = new AppControl();
678             /// string[] myValues = new string[] { "first", "second", "third" };
679             /// appControl.ExtraData.Add("myKey", myValues);
680             /// </code>
681             /// </example>
682             public void Add(string key, IEnumerable<string> value)
683             {
684                 if (string.IsNullOrEmpty(key))
685                 {
686                     throw new ArgumentNullException("key");
687                 }
688                 if (value == null)
689                 {
690                     throw new ArgumentNullException("value");
691                 }
692                 string[] valueArray = value.ToArray();
693                 Interop.AppControl.ErrorCode err = Interop.AppControl.AddExtraDataArray(_handle, key, valueArray, valueArray.Length);
694                 if (err != Interop.AppControl.ErrorCode.None)
695                 {
696                     switch (err)
697                     {
698                         case Interop.AppControl.ErrorCode.InvalidParameter:
699                             throw new ArgumentException("Invalid parameter: key or value is a zero-length string");
700                         case Interop.AppControl.ErrorCode.KeyRejected:
701                             throw new ArgumentException("Key is rejected: the key is system-defined key.");
702                         default:
703                             throw new InvalidOperationException("Error = " + err);
704                     }
705                 }
706             }
707
708             /// <summary>
709             /// Gets the extra data.
710             /// </summary>
711             /// <typeparam name="T">Only string and IEnumerable&lt;string&gt;</typeparam>
712             /// <param name="key">The name of extra data</param>
713             /// <returns>The value associated with the given key</returns>
714             /// <exception cref="ArgumentNullException">Thrown when the key is invalid parameter</exception>
715             /// <exception cref="KeyNotFoundException">Thrown when the key is not found</exception>
716             /// <exception cref="ArgumentException">Thrown when the key is rejected</exception>
717             /// <example>
718             /// <code>
719             /// AppControl appControl = new AppControl();
720             /// string myValue = appControl.ExtraData.Get<string>("myKey");
721             /// </code>
722             /// </example>
723             public T Get<T>(string key)
724             {
725                 object ret = Get(key);
726                 return (T)ret;
727             }
728
729             /// <summary>
730             /// Gets the extra data.
731             /// </summary>
732             /// <param name="key">The name of extra data</param>
733             /// <returns>The value associated with the given key</returns>
734             /// <exception cref="ArgumentNullException">Thrown when the key is invalid parameter</exception>
735             /// <exception cref="KeyNotFoundException">Thrown when the key is not found</exception>
736             /// <exception cref="ArgumentException">Thrown when the key is rejected</exception>
737             /// <example>
738             /// <code>
739             /// AppControl appControl = new AppControl();
740             /// string myValue = appControl.ExtraData.Get("myKey") as string;
741             /// if (myValue != null)
742             /// {
743             ///     // ...
744             /// }
745             /// </code>
746             /// </example>
747             public object Get(string key)
748             {
749                 if (IsCollection(key))
750                 {
751                     return GetDataCollection(key);
752                 }
753                 else
754                 {
755                     return GetData(key);
756                 }
757             }
758
759             /// <summary>
760             /// Gets all keys in extra data.
761             /// </summary>
762             /// <returns>The keys in the AppControl</returns>
763             /// <exception cref="InvalidOperationException">Thrown when invalid parameter</exception>
764             /// <example>
765             /// <code>
766             /// AppControl appControl = new AppControl();
767             /// IEnumerable<string> keys = appControl.GetKeys();
768             /// if (keys != null)
769             /// {
770             ///     foreach (string key in keys)
771             ///     {
772             ///         // ...
773             ///     }
774             /// }
775             /// </code>
776             /// </example>
777             public IEnumerable<string> GetKeys()
778             {
779                 List<string> keys = new List<string>();
780                 Interop.AppControl.ExtraDataCallback callback = (handle, key, userData) =>
781                 {
782                     if (key == null)
783                     {
784                         return false;
785                     }
786
787                     keys.Add(key);
788                     return true;
789                 };
790
791                 Interop.AppControl.ErrorCode err = Interop.AppControl.ForeachExtraData(_handle, callback, IntPtr.Zero);
792                 if (err != Interop.AppControl.ErrorCode.None)
793                 {
794                     throw new InvalidOperationException("Failed to get keys. err = " + err);
795                 }
796
797                 return keys;
798             }
799
800             /// <summary>
801             /// Tries getting the extra data.
802             /// </summary>
803             /// <param name="key">The name of extra data</param>
804             /// <param name="value">The value associated with the given key</param>
805             /// <returns>The result whether getting the value is done</returns>
806             /// <exception cref="ArgumentNullException">Thrown when the key is invalid parameter</exception>
807             /// <exception cref="KeyNotFoundException">Thrown when the key is not found</exception>
808             /// <exception cref="ArgumentException">Thrown when the key is rejected</exception>
809             /// <example>
810             /// <code>
811             /// AppControl appControl = new AppControl();
812             /// string myValue = string.Empty;
813             /// bool result = appControl.ExtraData.TryGet("myKey", out myValue);
814             /// if (result != null)
815             /// {
816             ///     // ...
817             /// }
818             /// </code>
819             /// </example>
820             public bool TryGet(string key, out string value)
821             {
822                 if (string.IsNullOrEmpty(key))
823                 {
824                     throw new ArgumentNullException("key");
825                 }
826                 Interop.AppControl.GetExtraData(_handle, key, out value);
827                 if (value != null)
828                 {
829                     return true;
830                 }
831                 else
832                 {
833                     value = default(string);
834                     return false;
835                 }
836             }
837
838             /// <summary>
839             /// Tries getting the extra data.
840             /// </summary>
841             /// <param name="key">The name of extra data</param>
842             /// <param name="value">The value associated with the given key</param>
843             /// <returns>The result whether getting the value is done</returns>
844             /// <exception cref="ArgumentNullException">Thrown when the key is invalid parameter</exception>
845             /// <exception cref="KeyNotFoundException">Thrown when the key is not found</exception>
846             /// <exception cref="ArgumentException">Thrown when the key is rejected</exception>
847             /// <example>
848             /// <code>
849             /// AppControl appControl = new AppControl();
850             /// IEnumerable<string> myValue = null;
851             /// bool result = appControl.ExtraData.TryGet("myKey", out myValue);
852             /// if (result)
853             /// {
854             ///     foreach (string value in myValue)
855             ///     {
856             ///         // ...
857             ///     }
858             /// }
859             /// </code>
860             /// </example>
861             public bool TryGet(string key, out IEnumerable<string> value)
862             {
863                 if (string.IsNullOrEmpty(key))
864                 {
865                     throw new ArgumentNullException("key");
866                 }
867                 IntPtr valuePtr = IntPtr.Zero;
868                 int len = -1;
869                 Interop.AppControl.ErrorCode err = Interop.AppControl.GetExtraDataArray(_handle, key, out valuePtr, out len);
870                 if (err == Interop.AppControl.ErrorCode.None && valuePtr != IntPtr.Zero)
871                 {
872                     List<string> stringList = new List<string>();
873                     for (int i = 0; i < len; ++i)
874                     {
875                         IntPtr charArr = Marshal.ReadIntPtr(valuePtr, IntPtr.Size * i);
876                         stringList.Add(Marshal.PtrToStringAnsi(charArr));
877                         Interop.Libc.Free(charArr);
878                     }
879                     Interop.Libc.Free(valuePtr);
880                     value = stringList;
881                     return true;
882                 }
883                 else
884                 {
885                     value = default(IEnumerable<string>);
886                     return false;
887                 }
888             }
889
890             /// <summary>
891             /// Removes the extra data.
892             /// </summary>
893             /// <param name="key">The name of the extra data</param>
894             /// <exception cref="ArgumentNullException">Thrown when the key is a zero-length string</exception>
895             /// <exception cref="KeyNotFoundException">Thrown when the key is not found</exception>
896             /// <exception cref="ArgumentException">Thrown when the key is rejected</exception>
897             /// <example>
898             /// <code>
899             /// AppControl appControl = new AppControl();
900             /// appControl.ExtraData.Remove("myKey");
901             /// </code>
902             /// </example>
903             public void Remove(string key)
904             {
905                 if (string.IsNullOrEmpty(key))
906                 {
907                     throw new ArgumentNullException("key");
908                 }
909                 Interop.AppControl.ErrorCode err = Interop.AppControl.RemoveExtraData(_handle, key);
910                 if (err != Interop.AppControl.ErrorCode.None)
911                 {
912                     switch (err)
913                     {
914                         case Interop.AppControl.ErrorCode.InvalidParameter:
915                             throw new ArgumentException("Invalid parameter: key is a zero-length string");
916                         case Interop.AppControl.ErrorCode.KeyNotFound:
917                             throw new KeyNotFoundException("Key is not found"); ;
918                         case Interop.AppControl.ErrorCode.KeyRejected:
919                             throw new ArgumentException("Key is rejected: the key is system-defined key.");
920                         default:
921                             throw new InvalidOperationException("Error = " + err);
922                     }
923                 }
924             }
925
926             /// <summary>
927             /// Counts keys in the extra data.
928             /// </summary>
929             /// <returns>The number of counting keys</returns>
930             /// <exception cref="InvalidOperationException">Thrown when invalid parameter</exception>
931             /// <example>
932             /// <code>
933             /// AppControl appControl = new AppControl();
934             /// int numberOfKeys = appControl.ExtraData.Count();
935             /// </code>
936             /// </example>
937             public int Count()
938             {
939                 return GetKeys().Count();
940             }
941
942             /// <summary>
943             /// Checks whether the extra data associated with the given key is of collection data type.
944             /// </summary>
945             /// <param name="key">The name of the extra data</param>
946             /// <returns>If true the extra data is of array data type, otherwise false</returns>
947             /// <exception cref="ArgumentNullException">Thrown when the key is a zero-length string</exception>
948             /// <exception cref="InvalidOperationException">Thrown when failed to check the key</exception>
949             /// <example>
950             /// <code>
951             /// AppControl appControl = new AppControl();
952             /// bool result = appControl.ExtraData.IsCollection("myKey");
953             /// </code>
954             /// </example>
955             public bool IsCollection(string key)
956             {
957                 if (string.IsNullOrEmpty(key))
958                 {
959                     throw new ArgumentNullException("key");
960                 }
961                 bool isArray = false;
962                 Interop.AppControl.ErrorCode err = Interop.AppControl.IsExtraDataArray(_handle, key, out isArray);
963                 if (err != Interop.AppControl.ErrorCode.None)
964                 {
965                     throw new InvalidOperationException("Error = " + err);
966                 }
967                 return isArray;
968             }
969
970             private string GetData(string key)
971             {
972                 if (string.IsNullOrEmpty(key))
973                 {
974                     throw new ArgumentNullException("key");
975                 }
976                 string value = string.Empty;
977                 Interop.AppControl.ErrorCode err = Interop.AppControl.GetExtraData(_handle, key, out value);
978                 if (err != Interop.AppControl.ErrorCode.None)
979                 {
980                     switch (err)
981                     {
982                         case Interop.AppControl.ErrorCode.InvalidParameter:
983                             throw new ArgumentException("Invalid parameter: key is a zero-length string");
984                         case Interop.AppControl.ErrorCode.KeyNotFound:
985                             throw new KeyNotFoundException("Key is not found"); ;
986                         case Interop.AppControl.ErrorCode.InvalidDataType:
987                             throw new ArgumentException("Invalid data type: value is data collection type");
988                         case Interop.AppControl.ErrorCode.KeyRejected:
989                             throw new ArgumentException("Key is rejected: the key is system-defined key.");
990                         default:
991                             throw new InvalidOperationException("Error = " + err);
992                     }
993                 }
994                 return value;
995             }
996
997             private IEnumerable<string> GetDataCollection(string key)
998             {
999                 if (string.IsNullOrEmpty(key))
1000                 {
1001                     throw new ArgumentNullException("key");
1002                 }
1003                 IntPtr valuePtr = IntPtr.Zero;
1004                 int len = -1;
1005                 Interop.AppControl.ErrorCode err = Interop.AppControl.GetExtraDataArray(_handle, key, out valuePtr, out len);
1006                 if (err != Interop.AppControl.ErrorCode.None)
1007                 {
1008                     switch (err)
1009                     {
1010                         case Interop.AppControl.ErrorCode.InvalidParameter:
1011                             throw new ArgumentException("Invalid parameter: key is a zero-length string");
1012                         case Interop.AppControl.ErrorCode.KeyNotFound:
1013                             throw new KeyNotFoundException("Key is not found"); ;
1014                         case Interop.AppControl.ErrorCode.InvalidDataType:
1015                             throw new ArgumentException("Invalid data type: value is data collection type");
1016                         case Interop.AppControl.ErrorCode.KeyRejected:
1017                             throw new ArgumentException("Key is rejected: the key is system-defined key.");
1018                         default:
1019                             throw new InvalidOperationException("Error = " + err);
1020                     }
1021                 }
1022
1023                 List<string> valueArray = new List<string>();
1024                 if (valuePtr != IntPtr.Zero)
1025                 {
1026                     for (int i = 0; i < len; ++i)
1027                     {
1028                         IntPtr charArr = Marshal.ReadIntPtr(valuePtr, IntPtr.Size * i);
1029                         valueArray.Add(Marshal.PtrToStringAnsi(charArr));
1030                         Interop.Libc.Free(charArr);
1031                     }
1032                     Interop.Libc.Free(valuePtr);
1033                 }
1034                 return valueArray;
1035             }
1036         }
1037     }
1038 }