[Tizen.Multimedia] Fix XML Doc warnings
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Remoting / ScreenMirroring / ScreenMirroring.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.Diagnostics;
19 using System.Linq;
20 using System.Threading;
21 using System.Threading.Tasks;
22 using Native = Interop.ScreenMirroring;
23
24 namespace Tizen.Multimedia.Remoting
25 {
26     /// <summary>
27     /// Provides the ability to connect to and disconnect from a screen mirroring source,
28     /// start, pause, and resume the screen mirroring as a sink.
29     /// </summary>
30     public class ScreenMirroring : IDisposable, IDisplayable<ScreenMirroringErrorCode>
31     {
32         private const string Feature = "http://tizen.org/feature/network.wifi.direct.display";
33         private const int Port = 2022;
34
35         private IntPtr _handle;
36
37         private AtomicState _state;
38
39         private bool _disposed = false;
40
41         internal IntPtr Handle
42         {
43             get
44             {
45                 ThrowIfDisposed();
46
47                 return _handle;
48             }
49         }
50
51         private static bool IsSupported()
52         {
53             return System.Information.TryGetValue(Feature, out bool isSupported) ? isSupported : false;
54         }
55
56         /// <summary>
57         /// Initializes a new instance of the ScreenMirroring class.
58         /// </summary>
59         /// <feature>http://tizen.org/feature/network.wifi.direct.display</feature>
60         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
61         public ScreenMirroring()
62         {
63             if (IsSupported() == false)
64             {
65                 throw new PlatformNotSupportedException($"The feature({Feature}) is not supported on the current device");
66             }
67
68             Native.Create(out _handle).ThrowIfError("Failed to create ScreenMirroring.");
69
70             _state = new AtomicState();
71
72             AudioInfo = new ScreenMirroringAudioInfo(this);
73             VideoInfo = new ScreenMirroringVideoInfo(this);
74
75             RegisterStateChangedEvent();
76         }
77
78         /// <summary>
79         /// Finalizes an instance of the ScreenMirroring class.
80         /// </summary>
81         ~ScreenMirroring()
82         {
83             Dispose(false);
84         }
85
86         /// <summary>
87         /// Occurs when the state is changed.
88         /// </summary>
89         public event EventHandler<ScreenMirroringStateChangedEventArgs> StateChanged;
90
91         /// <summary>
92         /// Occurs when an error occurs.
93         /// </summary>
94         public event EventHandler<ScreenMirroringErrorOccurredEventArgs> ErrorOccurred;
95
96         #region Display support
97
98         private Display _display;
99
100         private void DetachDisplay()
101         {
102             if (_display != null)
103             {
104                 _display.SetOwner(null);
105                 _display = null;
106             }
107         }
108
109         private void SetDisplay(Display display)
110         {
111             if (display == null)
112             {
113                 throw new ArgumentNullException(nameof(Display));
114             }
115
116             display.SetOwner(this);
117             display.ApplyTo(this).ThrowIfError("Failed to set display.");
118
119             _display = display;
120         }
121
122         ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEvasDisplay(DisplayType type,
123             ElmSharp.EvasObject evasObject)
124         {
125             Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
126
127             return Native.SetDisplay(Handle, (int)type, evasObject);
128         }
129
130         ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEcoreWindow(IntPtr windowHandle)
131         {
132             throw new NotSupportedException("ScreenMirroring does not support NUI.Window display.");
133         }
134         #endregion
135
136         /// <summary>
137         /// Gets the negotiated audio info.
138         /// </summary>
139         public ScreenMirroringAudioInfo AudioInfo { get; }
140
141         /// <summary>
142         /// Gets the negotiated video info.
143         /// </summary>
144         public ScreenMirroringVideoInfo VideoInfo { get; }
145
146         private bool IsConnected
147         {
148             get
149             {
150                 return _state.IsOneOf(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
151                     ScreenMirroringState.Paused);
152             }
153         }
154
155         internal void ThrowIfNotConnected()
156         {
157             ThrowIfDisposed();
158
159             if (IsConnected == false)
160             {
161                 throw new InvalidOperationException("ScreenMirroring is not connected.");
162             }
163         }
164
165         /// <summary>
166         /// Prepares the screen mirroring with the specified display.
167         /// </summary>
168         /// <remarks>
169         /// The state must be <see cref="ScreenMirroringState.Idle"/>.\n
170         /// \n
171         /// All supported resolutions will be candidates.
172         /// </remarks>
173         /// <param name="display">The display where the mirroring will be played on.</param>
174         /// <exception cref="ArgumentException">
175         ///    <paramref name="display"/> has already been assigned to another.
176         /// </exception>
177         /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
178         /// <exception cref="InvalidOperationException">
179         ///     The current state is not in the valid.\n
180         ///     -or-\n
181         ///     An internal error occurs.
182         /// </exception>
183         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
184         public void Prepare(Display display)
185         {
186             PrepareCore(display, (ScreenMirroringResolutions)0);
187         }
188
189         /// <summary>
190         /// Prepares the screen mirroring with the specified display and resolutions.
191         /// </summary>
192         /// <remarks>
193         /// The state must be <see cref="ScreenMirroringState.Idle"/>.
194         /// </remarks>
195         /// <param name="display">The display where the mirroring will be played on.</param>
196         /// <param name="resolutions">The desired resolutions.</param>
197         /// <exception cref="ArgumentException">
198         ///    <paramref name="resolutions"/> contain invalid flags.\n
199         ///    -or-\n
200         ///    <paramref name="display"/> has already been assigned to another.
201         /// </exception>
202         /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
203         /// <exception cref="InvalidOperationException">
204         ///     The current state is not in the valid.\n
205         ///     -or-\n
206         ///     An internal error occurs.
207         /// </exception>
208         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
209         public void Prepare(Display display, ScreenMirroringResolutions resolutions)
210         {
211             ValidationUtil.ValidateFlagsEnum(resolutions, (ScreenMirroringResolutions)((1 << 7) - 1), nameof(resolutions));
212
213             PrepareCore(display, resolutions);
214         }
215
216         private void PrepareCore(Display display, ScreenMirroringResolutions resolutions)
217         {
218             ValidateState(ScreenMirroringState.Idle);
219
220             Native.SetResolution(Handle, resolutions).ThrowIfError("Failed to set resolutions.");
221
222             try
223             {
224                 SetDisplay(display);
225
226                 Native.Prepare(Handle).ThrowIfError("Failed to prepare.");
227             }
228             catch
229             {
230                 DetachDisplay();
231                 throw;
232             }
233         }
234
235         /// <summary>
236         /// Creates the connection and ready for receiving data from a mirroring source.
237         /// </summary>
238         /// <param name="sourceIp">The source ip address to connect.</param>
239         /// <remarks>
240         /// The state must be <see cref="ScreenMirroringState.Prepared"/> state by
241         /// <see cref="Prepare(Display, ScreenMirroringResolutions)"/>.
242         /// </remarks>
243         /// <returns>A task that represents the asynchronous operation.</returns>
244         /// <privilege>http://tizen.org/privilege/internet</privilege>
245         /// <exception cref="ArgumentNullException"><paramref name="sourceIp"/> is null.</exception>
246         /// <exception cref="InvalidOperationException">
247         ///     The current state is not in the valid.\n
248         ///     -or-\n
249         ///     An internal error occurs.
250         /// </exception>
251         /// <exception cref="ArgumentException"><paramref name="sourceIp"/> is a zero-length string, contains only white space.</exception>
252         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
253         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
254         public Task ConnectAsync(string sourceIp)
255         {
256             if (sourceIp == null)
257             {
258                 throw new ArgumentNullException(nameof(sourceIp));
259             }
260
261             if (string.IsNullOrWhiteSpace(sourceIp))
262             {
263                 throw new ArgumentException($"{nameof(sourceIp)} is a zero-length string.", nameof(sourceIp));
264             }
265
266             ValidateState(ScreenMirroringState.Prepared);
267
268             Native.SetIpAndPort(Handle, sourceIp, Port.ToString()).ThrowIfError("Failed to set ip.");
269
270             var tcs = new TaskCompletionSource<bool>();
271
272             Task.Factory.StartNew(() =>
273             {
274                 Native.Connect(Handle).ThrowIfError("Failed to connect");
275                 tcs.SetResult(true);
276             });
277
278             return tcs.Task;
279         }
280
281         /// <summary>
282         /// Starts mirroring from the source.
283         /// </summary>
284         /// <remarks>
285         /// The state must be <see cref="ScreenMirroringState.Connected"/> state by
286         /// <see cref="ConnectAsync(string)"/>.
287         /// </remarks>
288         /// <returns>A task that represents the asynchronous operation.</returns>
289         /// <privilege>http://tizen.org/privilege/internet</privilege>
290         /// <exception cref="InvalidOperationException">
291         ///     The current state is not in the valid.\n
292         ///     -or-\n
293         ///     An internal error occurs.
294         /// </exception>
295         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
296         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
297         public Task StartAsync()
298         {
299             ValidateState(ScreenMirroringState.Connected);
300
301             var tcs = new TaskCompletionSource<bool>();
302
303             Task.Factory.StartNew(() =>
304             {
305                 Native.StartAsync(Handle).ThrowIfError("Failed to start.");
306                 tcs.TrySetResult(true);
307             });
308
309             return tcs.Task;
310         }
311
312         /// <summary>
313         /// Pauses mirroring from the source.
314         /// </summary>
315         /// <remarks>
316         /// The state must be <see cref="ScreenMirroringState.Playing"/> state by
317         /// <see cref="StartAsync"/>.
318         /// </remarks>
319         /// <returns>A task that represents the asynchronous operation.</returns>
320         /// <privilege>http://tizen.org/privilege/internet</privilege>
321         /// <exception cref="InvalidOperationException">
322         ///     The current state is not in the valid.\n
323         ///     -or-\n
324         ///     An internal error occurs.
325         /// </exception>
326         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
327         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
328         public Task PauseAsync()
329         {
330             ValidateState(ScreenMirroringState.Playing);
331
332             var tcs = new TaskCompletionSource<bool>();
333
334             Task.Factory.StartNew(() =>
335             {
336                 Native.PauseAsync(Handle).ThrowIfError("Failed to prepare.");
337                 tcs.TrySetResult(true);
338             });
339
340             return tcs.Task;
341         }
342
343         /// <summary>
344         /// Resumes mirroring from the source.
345         /// </summary>
346         /// <remarks>
347         /// The state must be <see cref="ScreenMirroringState.Paused"/> state by
348         /// <see cref="PauseAsync"/>.
349         /// </remarks>
350         /// <returns>A task that represents the asynchronous operation.</returns>
351         /// <privilege>http://tizen.org/privilege/internet</privilege>
352         /// <exception cref="InvalidOperationException">
353         ///     The current state is not in the valid.\n
354         ///     -or-\n
355         ///     An internal error occurs.
356         /// </exception>
357         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
358         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
359         public Task ResumeAsync()
360         {
361             ValidateState(ScreenMirroringState.Paused);
362
363             var tcs = new TaskCompletionSource<bool>();
364
365             Task.Factory.StartNew(() =>
366             {
367                 Native.ResumeAsync(Handle).ThrowIfError("Failed to resume.");
368                 tcs.TrySetResult(true);
369             });
370
371             return tcs.Task;
372         }
373
374         /// <summary>
375         /// Disconnects from the source.
376         /// </summary>
377         /// <remarks>
378         /// The state must be <see cref="ScreenMirroringState.Connected"/>,
379         /// <see cref="ScreenMirroringState.Playing"/> or <see cref="ScreenMirroringState.Paused"/>.
380         /// </remarks>
381         /// <privilege>http://tizen.org/privilege/internet</privilege>
382         /// <exception cref="InvalidOperationException">
383         ///     The current state is not in the valid.\n
384         ///     -or-\n
385         ///     An internal error occurs.
386         /// </exception>
387         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
388         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
389         public void Disconnect()
390         {
391             ValidateState(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
392                 ScreenMirroringState.Paused);
393
394             Native.Disconnect(Handle).ThrowIfError("Failed to disconnect.");
395         }
396
397         /// <summary>
398         /// Unprepares the screen mirroring.
399         /// </summary>
400         /// <remarks>
401         /// The state must be <see cref="ScreenMirroringState.Prepared"/>,
402         /// or <see cref="ScreenMirroringState.Disconnected"/>.
403         /// </remarks>
404         /// <exception cref="InvalidOperationException">
405         ///     The current state is not in the valid.\n
406         ///     -or-\n
407         ///     An internal error occurs.
408         /// </exception>
409         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
410         public void Unprepare()
411         {
412             ValidateState(ScreenMirroringState.Prepared, ScreenMirroringState.Disconnected);
413
414             Native.Unprepare(Handle).ThrowIfError("Failed to reset.");
415
416             DetachDisplay();
417         }
418
419         private void ThrowIfDisposed()
420         {
421             if (_disposed)
422             {
423                 throw new ObjectDisposedException(nameof(ScreenMirroring));
424             }
425         }
426
427         /// <summary>
428         /// Releases all resource used by the <see cref="ScreenMirroring"/> object.
429         /// </summary>
430         /// <remarks>
431         /// Call <see cref="Dispose()"/> when you are finished using the <see cref="ScreenMirroring"/>.
432         /// The <see cref="Dispose()"/> method leaves the <see cref="ScreenMirroring"/> in an unusable
433         /// state. After calling <see cref="Dispose()"/>, you must release all references to the
434         /// <see cref="ScreenMirroring"/> so the garbage collector can reclaim the memory that the
435         /// <see cref="ScreenMirroring"/> was occupying.
436         /// </remarks>
437         public void Dispose()
438         {
439             Dispose(true);
440             GC.SuppressFinalize(this);
441         }
442
443         /// <summary>
444         /// Releases the resources used by the ScreenMirroring.
445         /// </summary>
446         /// <param name="disposing">
447         /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
448         /// </param>
449         protected virtual void Dispose(bool disposing)
450         {
451             if (!_disposed)
452             {
453                 DetachDisplay();
454
455                 if (_handle != IntPtr.Zero)
456                 {
457                     Native.Destroy(_handle);
458                     _handle = IntPtr.Zero;
459                 }
460
461                 _disposed = true;
462             }
463         }
464
465         private Native.StateChangedCallback _stateChangedCallback;
466
467         private void RegisterStateChangedEvent()
468         {
469             _stateChangedCallback = (error, state, _) =>
470             {
471                 var prevState = _state.Value;
472
473                 _state.Value = state;
474
475                 if (prevState != state)
476                 {
477                     StateChanged?.Invoke(this, new ScreenMirroringStateChangedEventArgs(state));
478
479                 }
480
481                 if (error != ScreenMirroringErrorCode.None)
482                 {
483                     ErrorOccurred?.Invoke(this, new ScreenMirroringErrorOccurredEventArgs(
484                         ScreenMirroringError.InvalidOperation));
485                 }
486             };
487
488             Native.SetStateChangedCb(Handle, _stateChangedCallback).
489                 ThrowIfError("Failed to initialize StateChanged event.");
490         }
491
492         private void ValidateState(params ScreenMirroringState[] required)
493         {
494             Debug.Assert(required.Length > 0);
495
496             if (_disposed)
497             {
498                 throw new ObjectDisposedException(nameof(ScreenMirroring));
499             }
500
501             var curState = _state.Value;
502             if (!required.Contains(curState))
503             {
504                 throw new InvalidOperationException($"The screen mirroring is not in a valid state. " +
505                     $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
506             }
507         }
508
509     }
510
511     internal class AtomicState
512     {
513         private int _value;
514
515         public AtomicState()
516         {
517             _value = (int)ScreenMirroringState.Idle;
518         }
519
520         public ScreenMirroringState Value
521         {
522             get
523             {
524                 return (ScreenMirroringState)Interlocked.CompareExchange(ref _value, 0, 0);
525             }
526             set
527             {
528                 Interlocked.Exchange(ref _value, (int)value);
529             }
530         }
531
532         public bool IsOneOf(params ScreenMirroringState[] states)
533         {
534             return states.Contains(Value);
535         }
536     }
537 }