2 * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using System.Collections.ObjectModel;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.Diagnostics;
22 using System.Runtime.InteropServices;
23 using System.Threading;
24 using System.Threading.Tasks;
25 using Tizen.Applications;
28 namespace Tizen.Multimedia.Remoting
30 internal static class WebRTCLog
32 internal const string Tag = "Tizen.Multimedia.WebRTC";
36 /// Provides the ability to control WebRTC.
38 /// <since_tizen> 9 </since_tizen>
39 public partial class WebRTC : IDisposable
41 private readonly WebRTCHandle _handle;
42 private List<MediaSource> _source;
45 /// Initializes a new instance of the <see cref="WebRTC"/> class.
47 /// <feature>http://tizen.org/feature/network.wifi</feature>
48 /// <feature>http://tizen.org/feature/network.telephony</feature>
49 /// <feature>http://tizen.org/feature/network.ethernet</feature>
50 /// <privilege>http://tizen.org/privilege/internet</privilege>
51 /// <exception cref="UnauthorizedAccessException">Thrown when the permission is denied.</exception>
52 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
53 /// <since_tizen> 9 </since_tizen>
56 if (!Features.IsSupported(WebRTCFeatures.Wifi) &&
57 !Features.IsSupported(WebRTCFeatures.Telephony) &&
58 !Features.IsSupported(WebRTCFeatures.Ethernet))
60 throw new NotSupportedException("Network features are not supported.");
63 NativeWebRTC.Create(out _handle).ThrowIfFailed("Failed to create webrtc");
65 Debug.Assert(_handle != null);
69 _source = new List<MediaSource>();
72 internal void ValidateWebRTCState(params WebRTCState[] desiredStates)
74 Debug.Assert(desiredStates.Length > 0);
76 ValidateNotDisposed();
78 WebRTCState curState = State;
79 if (!curState.IsAnyOf(desiredStates))
81 throw new InvalidOperationException("The WebRTC is not in a valid state. " +
82 $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
86 #region Dispose support
87 private bool _disposed;
90 /// Releases all resources used by the current instance.
92 /// <since_tizen> 9 </since_tizen>
96 GC.SuppressFinalize(this);
100 /// Releases the unmanaged resources used by the <see cref="WebRTC"/>.
102 /// <param name="disposing">
103 /// true to release both managed and unmanaged resources;
104 /// false to release only unmanaged resources.
106 [EditorBrowsable(EditorBrowsableState.Never)]
107 protected virtual void Dispose(bool disposing)
109 if (_disposed || !disposing)
119 internal void ValidateNotDisposed()
123 Log.Warn(WebRTCLog.Tag, "WebRTC was disposed");
124 throw new ObjectDisposedException(nameof(WebRTC));
128 internal bool IsDisposed => _disposed;
132 /// Starts the WebRTC.
135 /// The WebRTC must be in the <see cref="WebRTCState.Idle"/> state.<br/>
136 /// The WebRTC state will be <see cref="WebRTCState.Negotiating"/> state.<br/>
137 /// The user should check whether <see cref="State" /> is changed to <see cref="WebRTCState.Negotiating"/> state or not.
139 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
140 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
141 /// <see also="WebRTCState"/>
142 /// <see also="StateChanged"/>
143 /// <see also="CreateOffer"/>
144 /// <since_tizen> 9 </since_tizen>
147 ValidateWebRTCState(WebRTCState.Idle);
149 NativeWebRTC.Start(Handle).ThrowIfFailed("Failed to start the WebRTC");
153 /// Starts the WebRTC asynchronously.
156 /// The WebRTC must be in the <see cref="WebRTCState.Idle"/> state.<br/>
157 /// The WebRTC state will be <see cref="WebRTCState.Negotiating"/> state.<br/>
158 /// This ensures that <see cref="State" /> is changed to <see cref="WebRTCState.Negotiating"/> state.
160 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
161 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
162 /// <see also="WebRTCState"/>
163 /// <see also="CreateOffer"/>
164 /// <since_tizen> 9 </since_tizen>
165 public async Task StartAsync()
167 ValidateWebRTCState(WebRTCState.Idle);
169 var tcs = new TaskCompletionSource<bool>();
170 var error = WebRTCError.ConnectionFailed;
172 EventHandler<WebRTCStateChangedEventArgs> stateChangedEventHandler = (s, e) =>
174 if (e.Current == WebRTCState.Negotiating)
176 tcs.TrySetResult(true);
179 EventHandler<WebRTCErrorOccurredEventArgs> errorEventHandler = (s, e) =>
181 Log.Info(WebRTCLog.Tag, e.ToString());
183 tcs.TrySetResult(false);
188 StateChanged += stateChangedEventHandler;
189 ErrorOccurred += errorEventHandler;
191 NativeWebRTC.Start(Handle).ThrowIfFailed("Failed to start the WebRTC");
193 var result = await tcs.Task.ConfigureAwait(false);
198 throw new InvalidOperationException(error.ToString());
203 StateChanged -= stateChangedEventHandler;
204 ErrorOccurred -= errorEventHandler;
209 /// Stops the WebRTC.
212 /// The WebRTC must be in the <see cref="WebRTCState.Negotiating"/> or <see cref="WebRTCState.Playing"/> state.<br/>
213 /// The WebRTC state will be <see cref="WebRTCState.Idle"/> state.<br/>
214 /// The user should check whether <see cref="State" /> is changed to <see cref="WebRTCState.Idle"/> state or not.
216 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
217 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
218 /// <since_tizen> 9 </since_tizen>
221 ValidateWebRTCState(WebRTCState.Negotiating, WebRTCState.Playing);
223 NativeWebRTC.Stop(Handle).ThrowIfFailed("Failed to stop the WebRTC");
227 /// Creates SDP offer asynchronously to start a new WebRTC connection to a remote peer.
229 /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/></remarks>
230 /// <returns>The SDP offer.</returns>
231 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
232 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
233 /// <seealso cref="CreateAnswerAsync()"/>
234 /// <since_tizen> 9 </since_tizen>
235 public async Task<string> CreateOfferAsync()
237 ValidateWebRTCState(WebRTCState.Negotiating);
239 var tcsSdpCreated = new TaskCompletionSource<string>();
241 NativeWebRTC.SdpCreatedCallback cb = (handle, sdp, _) =>
243 tcsSdpCreated.TrySetResult(sdp);
247 using (var cbKeeper = ObjectKeeper.Get(cb))
249 NativeWebRTC.CreateSDPOfferAsync(Handle, new SafeBundleHandle(), cb, IntPtr.Zero).
250 ThrowIfFailed("Failed to create offer asynchronously");
252 offer = await tcsSdpCreated.Task.ConfigureAwait(false);
260 /// Creates SDP answer asynchronously with option to an offer received from a remote peer.
262 /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/></remarks>
263 /// <returns>The SDP answer.</returns>
264 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
265 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
266 /// <seealso cref="CreateOfferAsync()"/>
267 /// <since_tizen> 9 </since_tizen>
268 public async Task<string> CreateAnswerAsync()
270 ValidateWebRTCState(WebRTCState.Negotiating);
272 var tcsSdpCreated = new TaskCompletionSource<string>();
274 NativeWebRTC.SdpCreatedCallback cb = (handle, sdp, _) =>
276 tcsSdpCreated.TrySetResult(sdp);
279 string answer = null;
280 using (var cbKeeper = ObjectKeeper.Get(cb))
282 NativeWebRTC.CreateSDPAnswerAsync(Handle, new SafeBundleHandle(), cb, IntPtr.Zero).
283 ThrowIfFailed("Failed to create answer asynchronously");
285 answer = await tcsSdpCreated.Task.ConfigureAwait(false);
293 /// Sets the session description for a local peer.
295 /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/>.</remarks>
296 /// <param name="description">The local session description.</param>
297 /// <exception cref="ArgumentException">The description is empty string.</exception>
298 /// <exception cref="ArgumentNullException">The description is null.</exception>
299 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
300 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
301 /// <seealso cref="CreateOfferAsync()"/>
302 /// <seealso cref="CreateAnswerAsync()"/>
303 /// <since_tizen> 9 </since_tizen>
304 public void SetLocalDescription(string description)
306 ValidateWebRTCState(WebRTCState.Negotiating);
308 ValidationUtil.ValidateIsNullOrEmpty(description, nameof(description));
310 NativeWebRTC.SetLocalDescription(Handle, description).ThrowIfFailed("Failed to set description.");
314 /// Sets the session description of the remote peer's current offer or answer.
316 /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/>.</remarks>
317 /// <param name="description">The remote session description.</param>
318 /// <exception cref="ArgumentException">The description is empty string.</exception>
319 /// <exception cref="ArgumentNullException">The description is null.</exception>
320 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
321 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
322 /// <seealso cref="CreateOfferAsync()"/>
323 /// <seealso cref="CreateAnswerAsync()"/>
324 /// <since_tizen> 9 </since_tizen>
325 public void SetRemoteDescription(string description)
327 ValidateWebRTCState(WebRTCState.Negotiating);
329 ValidationUtil.ValidateIsNullOrEmpty(description, nameof(description));
331 NativeWebRTC.SetRemoteDescription(Handle, description).ThrowIfFailed("Failed to set description.");
335 /// Adds a new ICE candidate from the remote peer over its signaling channel.
337 /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/>.</remarks>
338 /// <param name="iceCandidate">The ICE candidate.</param>
339 /// <exception cref="ArgumentException">The ICE candidate is empty string.</exception>
340 /// <exception cref="ArgumentNullException">The ICE candidate is null.</exception>
341 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
342 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
343 /// <since_tizen> 9 </since_tizen>
344 public void AddIceCandidate(string iceCandidate)
346 ValidateWebRTCState(WebRTCState.Negotiating);
348 ValidationUtil.ValidateIsNullOrEmpty(iceCandidate, nameof(iceCandidate));
350 NativeWebRTC.AddIceCandidate(Handle, iceCandidate).ThrowIfFailed("Failed to set ICE candidate.");
354 /// Adds new ICE candidates from the remote peer over its signaling channel.
356 /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/>.</remarks>
357 /// <param name="iceCandidates">The ICE candidates.</param>
358 /// <exception cref="ArgumentException">The ICE candidate is empty string.</exception>
359 /// <exception cref="ArgumentNullException">The ICE candidate is null.</exception>
360 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
361 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
362 /// <since_tizen> 9 </since_tizen>
363 public void AddIceCandidates(IEnumerable<string> iceCandidates)
365 ValidateWebRTCState(WebRTCState.Negotiating);
367 ValidationUtil.ValidateIsAny(iceCandidates);
369 #pragma warning disable CA1062
370 foreach (string iceCandidate in iceCandidates)
372 AddIceCandidate(iceCandidate);
374 #pragma warning restore CA1062
378 /// Adds media source.
381 /// The WebRTC must be in the <see cref="WebRTCState.Idle"/>.<br/>
382 /// Each MediaSource requires different feature or privilege.<br/>
383 /// <see cref="MediaCameraSource"/> needs camera feature and privilege.<br/>
384 /// <see cref="MediaMicrophoneSource"/> needs microphone feature and recorder privilege.<br/>
386 /// <param name="source">The media sources to add.</param>
387 /// <feature>http://tizen.org/feature/camera</feature>
388 /// <feature>http://tizen.org/feature/microphone</feature>
389 /// <feature>http://tizen.org/feature/display</feature>
390 /// <privilege>http://tizen.org/privilege/camera</privilege>
391 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
392 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
393 /// <privilege>http://tizen.org/privilege/recorder</privilege>
394 /// <exception cref="ArgumentNullException">The media source is null.</exception>
395 /// <exception cref="InvalidOperationException">
396 /// The WebRTC is not in the valid state.<br/>
398 /// All or one of <paramref name="source"/> was already detached.
400 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
401 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
402 /// <exception cref="UnauthorizedAccessException">Thrown when the permission is denied.</exception>
403 /// <seealso cref="MediaCameraSource"/>
404 /// <seealso cref="MediaMicrophoneSource"/>
405 /// <seealso cref="MediaTestSource"/>
406 /// <seealso cref="MediaPacketSource"/>
407 /// <seealso cref="AddSources"/>
408 /// <seealso cref="RemoveSource"/>
409 /// <seealso cref="RemoveSources"/>
410 /// <since_tizen> 9 </since_tizen>
411 public void AddSource(MediaSource source)
415 throw new ArgumentNullException(nameof(source), "source is null");
418 ValidateWebRTCState(WebRTCState.Idle);
420 source?.AttachTo(this);
426 /// Adds media sources.
429 /// The WebRTC must be in the <see cref="WebRTCState.Idle"/>.<br/>
430 /// Each MediaSource requires different feature or privilege.<br/>
431 /// <see cref="MediaCameraSource"/> needs camera feature and privilege.<br/>
432 /// <see cref="MediaMicrophoneSource"/> needs microphone feature and recorder privilege.<br/>
434 /// <param name="sources">The media sources to add.</param>
435 /// <feature>http://tizen.org/feature/camera</feature>
436 /// <feature>http://tizen.org/feature/microphone</feature>
437 /// <feature>http://tizen.org/feature/display</feature>
438 /// <privilege>http://tizen.org/privilege/camera</privilege>
439 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
440 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
441 /// <privilege>http://tizen.org/privilege/recorder</privilege>
442 /// <exception cref="ArgumentNullException">The media source is null.</exception>
443 /// <exception cref="InvalidOperationException">
444 /// The WebRTC is not in the valid state.<br/>
446 /// All or one of <paramref name="sources"/> was already detached.
448 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
449 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
450 /// <exception cref="UnauthorizedAccessException">Thrown when the permission is denied.</exception>
451 /// <seealso cref="MediaCameraSource"/>
452 /// <seealso cref="MediaMicrophoneSource"/>
453 /// <seealso cref="MediaTestSource"/>
454 /// <seealso cref="MediaPacketSource"/>
455 /// <seealso cref="AddSource"/>
456 /// <seealso cref="RemoveSource"/>
457 /// <seealso cref="RemoveSources"/>
458 /// <since_tizen> 9 </since_tizen>
459 public void AddSources(params MediaSource[] sources)
463 throw new ArgumentNullException(nameof(sources), "sources are null");
466 foreach (var source in sources)
473 /// Removes media source.
476 /// The WebRTC must be in the <see cref="WebRTCState.Idle"/>.<br/>
477 /// If user want to use removed MediaSource again, user should create new instance for it.
479 /// <param name="source">The media source to remove.</param>
480 /// <exception cref="ArgumentNullException">The media source is null.</exception>
481 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
482 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
483 /// <seealso cref="MediaCameraSource"/>
484 /// <seealso cref="MediaMicrophoneSource"/>
485 /// <seealso cref="MediaTestSource"/>
486 /// <seealso cref="MediaPacketSource"/>
487 /// <seealso cref="AddSource"/>
488 /// <seealso cref="AddSources"/>
489 /// <seealso cref="RemoveSources"/>
490 /// <since_tizen> 9 </since_tizen>
491 public void RemoveSource(MediaSource source)
495 throw new ArgumentNullException(nameof(source), "source is null");
498 ValidateWebRTCState(WebRTCState.Idle);
500 source?.DetachFrom(this);
502 _source.Remove(source);
508 /// Removes media sources.
511 /// The WebRTC must be in the <see cref="WebRTCState.Idle"/>.<br/>
512 /// If user want to use removed MediaSource again, user should create new instance for it.
514 /// <param name="sources">The media source to remove.</param>
515 /// <exception cref="ArgumentNullException">The media source is null.</exception>
516 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
517 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
518 /// <seealso cref="MediaCameraSource"/>
519 /// <seealso cref="MediaMicrophoneSource"/>
520 /// <seealso cref="MediaTestSource"/>
521 /// <seealso cref="MediaPacketSource"/>
522 /// <seealso cref="AddSource"/>
523 /// <seealso cref="AddSources"/>
524 /// <seealso cref="RemoveSource"/>
525 /// <since_tizen> 9 </since_tizen>
526 public void RemoveSources(params MediaSource[] sources)
528 foreach (var source in sources)
530 RemoveSource(source);
535 /// Sets a turn server.
537 /// <exception cref="ArgumentNullException">The <paramref name="turnServer"/> is null.</exception>
538 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
539 /// <since_tizen> 9 </since_tizen>
540 public void SetTurnServer(string turnServer)
542 ValidateNotDisposed();
544 if (turnServer == null)
546 throw new ArgumentNullException(nameof(turnServer), "Turn server name is null.");
549 NativeWebRTC.AddTurnServer(Handle, turnServer).
550 ThrowIfFailed("Failed to add turn server");
554 /// Sets turn servers.
556 /// <exception cref="ArgumentNullException">The one of <paramref name="turnServers"/> is null.</exception>
557 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
558 /// <since_tizen> 9 </since_tizen>
559 public void SetTurnServers(params string[] turnServers)
561 ValidateNotDisposed();
563 if (turnServers == null)
565 throw new ArgumentNullException(nameof(turnServers), "Turn server names are null.");
568 foreach (var turnServer in turnServers)
570 SetTurnServer(turnServer);
575 /// Retrieves all turn servers.
577 /// <returns>The turn server list.</returns>
578 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
579 /// <since_tizen> 9 </since_tizen>
580 public ReadOnlyCollection<string> GetTurnServer()
582 ValidateNotDisposed();
584 var list = new List<string>();
586 NativeWebRTC.RetrieveTurnServerCallback cb = (server, _) =>
588 if (!string.IsNullOrWhiteSpace(server))
596 NativeWebRTC.ForeachTurnServer(Handle, cb).ThrowIfFailed("Failed to retrieve turn server");
598 return list.AsReadOnly();
602 /// Retrieves the current statistics information.
604 /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Playing"/></remarks>
605 /// <returns>The WebRTC statistics informations.</returns>
606 /// <param name="category">The category of statistics to get.</param>
607 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
608 /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
609 /// <since_tizen> 10 </since_tizen>
610 public ReadOnlyCollection<WebRTCStatistics> GetStatistics(WebRTCStatisticsCategory category)
612 ValidateWebRTCState(WebRTCState.Playing);
614 var stats = new List<WebRTCStatistics>();
615 Exception caught = null;
617 NativeWebRTC.RetrieveStatsCallback cb = (category_, prop, _) =>
621 stats.Add(new WebRTCStatistics(category_, prop));
632 using (var cbKeeper = ObjectKeeper.Get(cb))
634 NativeWebRTC.ForeachStats(Handle, (int)category, cb, IntPtr.Zero).
635 ThrowIfFailed("failed to retrieve stats");
643 return new ReadOnlyCollection<WebRTCStatistics>(stats);
647 /// Represents WebRTC statistics information.
649 /// <since_tizen> 10 </since_tizen>
650 public class WebRTCStatistics
652 internal WebRTCStatistics(WebRTCStatisticsCategory type, IntPtr prop)
654 var unmanagedStruct = Marshal.PtrToStructure<NativeWebRTC.StatsPropertyStruct>(prop);
657 Name = unmanagedStruct.name;
658 Property = unmanagedStruct.property;
660 switch (unmanagedStruct.propertyType)
662 case WebRTCStatsPropertyType.TypeBool:
663 Value = unmanagedStruct.value.@bool;
665 case WebRTCStatsPropertyType.TypeInt:
666 Value = unmanagedStruct.value.@int;
668 case WebRTCStatsPropertyType.TypeUint:
669 Value = unmanagedStruct.value.@uint;
671 case WebRTCStatsPropertyType.TypeInt64:
672 Value = unmanagedStruct.value.@long;
674 case WebRTCStatsPropertyType.TypeUint64:
675 Value = unmanagedStruct.value.@ulong;
677 case WebRTCStatsPropertyType.TypeFloat:
678 Value = unmanagedStruct.value.@float;
680 case WebRTCStatsPropertyType.TypeDouble:
681 Value = unmanagedStruct.value.@double;
683 case WebRTCStatsPropertyType.TypeString:
684 Value = Marshal.PtrToStringAnsi(unmanagedStruct.value.@string);
687 throw new InvalidOperationException($"No matching type [{unmanagedStruct.propertyType}]");
692 /// Gets the category of statistics.
694 /// <value>The category of WebRTC statistics information</value>
695 /// <since_tizen> 10 </since_tizen>
696 public WebRTCStatisticsCategory Category { get; }
699 /// Gets the name of statistics.
701 /// <value>The name of WebRTC statistics information</value>
702 /// <since_tizen> 10 </since_tizen>
703 public string Name { get; }
706 /// Gets the property of statistics.
708 /// <value>The property of WebRTC statistics information</value>
709 /// <since_tizen> 10 </since_tizen>
710 public WebRTCStatisticsProperty Property { get; }
713 /// Gets the value of statistics.
715 /// <value>The value of WebRTC statistics information</value>
716 /// <since_tizen> 10 </since_tizen>
717 public object Value { get; }
720 /// Returns a string that represents the current object.
722 /// <returns>A string that represents the current object.</returns>
723 /// <since_tizen> 10 </since_tizen>
724 public override string ToString() =>
725 $"Category={Category}, Name={Name}, Property={Property}, Value={Value}, Type={Value.GetType()}";