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