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