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