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