[Multimedia] Fixed minor issues and clean codes in AudioManager. (#82)
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia / AudioManager / AudioStreamPolicy.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
20 namespace Tizen.Multimedia
21 {
22     /// <summary>
23     /// Provides the ability to control the sound stream.
24     /// </summary>
25     /// <since_tizen> 3 </since_tizen>
26     public class AudioStreamPolicy : IDisposable
27     {
28         private AudioStreamPolicyHandle _handle;
29         private bool _disposed = false;
30         private Interop.AudioStreamPolicy.FocusStateChangedCallback _focusStateChangedCallback;
31
32         /// <summary>
33         /// Initializes a new instance of the <see cref="AudioStreamPolicy"/> class with <see cref="AudioStreamType"/>.
34         /// </summary>
35         /// <remarks>
36         /// To apply the stream policy according to this stream information, the AudioStreamPolicy should
37         /// be passed to other APIs related to playback or recording. (For example., <see cref="T:Tizen.Multimedia.Player"/>,
38         /// <see cref="T:Tizen.Multimedia.WavPlayer"/> , etc.)
39         /// </remarks>
40         /// <param name="streamType">The type of the sound stream for which the policy needs to be created.</param>
41         /// <exception cref="ArgumentException"><paramref name="streamType"/> is invalid.</exception>
42         /// <since_tizen> 3 </since_tizen>
43         public AudioStreamPolicy(AudioStreamType streamType)
44         {
45             ValidationUtil.ValidateEnum(typeof(AudioStreamType), streamType, nameof(streamType));
46
47             _focusStateChangedCallback = (IntPtr streamInfo, AudioStreamFocusOptions focusMask,
48                 AudioStreamFocusState state, AudioStreamFocusChangedReason reason, AudioStreamBehaviors behaviors,
49                 string extraInfo, IntPtr _) =>
50             {
51                 FocusStateChanged?.Invoke(this,
52                     new AudioStreamPolicyFocusStateChangedEventArgs(focusMask, state, reason, behaviors, extraInfo));
53             };
54
55             Interop.AudioStreamPolicy.Create(streamType, _focusStateChangedCallback,
56                 IntPtr.Zero, out _handle).ThrowIfError("Unable to create stream information");
57
58             Debug.Assert(_handle != null);
59         }
60
61         /// <summary>
62         /// Occurs when the state of focus that belongs to the current AudioStreamPolicy is changed.
63         /// </summary>
64         /// <remarks>
65         /// The event is raised in the internal thread.
66         /// </remarks>
67         /// <since_tizen> 4 </since_tizen>
68         public event EventHandler<AudioStreamPolicyFocusStateChangedEventArgs> FocusStateChanged;
69
70         /// <summary>
71         /// Gets the <see cref="AudioVolumeType"/>.
72         /// </summary>
73         /// <remarks>
74         /// If the <see cref="AudioStreamType"/> of the current AudioStreamPolicy is <see cref="AudioStreamType.Emergency"/>,
75         /// it returns <see cref="AudioVolumeType.None"/>.
76         /// </remarks>
77         /// <value>The <see cref="AudioVolumeType"/> of the policy instance.</value>
78         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
79         /// <since_tizen> 3 </since_tizen>
80         public AudioVolumeType VolumeType
81         {
82             get
83             {
84                 var ret = Interop.AudioStreamPolicy.GetSoundType(Handle, out var type);
85                 if (ret == AudioManagerError.NoData)
86                 {
87                     return AudioVolumeType.None;
88                 }
89
90                 ret.ThrowIfError("Failed to get volume type");
91
92                 return type;
93             }
94         }
95
96         private AudioStreamFocusState GetFocusState(bool playback)
97         {
98             int ret = Interop.AudioStreamPolicy.GetFocusState(Handle, out var stateForPlayback, out var stateForRecording);
99             MultimediaDebug.AssertNoError(ret);
100
101             return playback ? stateForPlayback : stateForRecording;
102         }
103
104         /// <summary>
105         /// Gets the state of focus for the playback.
106         /// </summary>
107         /// <value>The state of focus for playback.</value>
108         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
109         /// <since_tizen> 3 </since_tizen>
110         public AudioStreamFocusState PlaybackFocusState => GetFocusState(true);
111
112         /// <summary>
113         /// Gets the state of focus for the recording.
114         /// </summary>
115         /// <value>The state of focus for recording.</value>
116         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
117         /// <since_tizen> 3 </since_tizen>
118         public AudioStreamFocusState RecordingFocusState => GetFocusState(false);
119
120         /// <summary>
121         /// Gets or sets the auto focus reacquisition.
122         /// </summary>
123         /// <value>
124         /// true if the auto focus reacquisition is enabled; otherwise, false.<br/>
125         /// The default is true.
126         /// </value>
127         /// <remarks>
128         /// If you don't want to reacquire the focus you've lost automatically,
129         /// disable the focus reacquisition.
130         /// </remarks>
131         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
132         /// <since_tizen> 3 </since_tizen>
133         public bool FocusReacquisitionEnabled
134         {
135             get
136             {
137                 Interop.AudioStreamPolicy.GetFocusReacquisition(Handle, out var enabled).
138                     ThrowIfError("Failed to get focus reacquisition state");
139
140                 return enabled;
141             }
142             set
143             {
144                 Interop.AudioStreamPolicy.SetFocusReacquisition(Handle, value).
145                     ThrowIfError("Failed to set focus reacquisition");
146             }
147         }
148
149         internal AudioStreamPolicyHandle Handle
150         {
151             get
152             {
153                 if (_disposed)
154                 {
155                     throw new ObjectDisposedException(nameof(AudioStreamPolicy));
156                 }
157                 return _handle;
158             }
159         }
160
161         /// <summary>
162         /// Acquires the stream focus.
163         /// </summary>
164         /// <param name="options">The focuses that you want to acquire.</param>
165         /// <param name="behaviors">The requesting behaviors.</param>
166         /// <param name="extraInfo">The extra information for this request. This value can be null.</param>
167         /// <exception cref="ArgumentException"><paramref name="options"/> is zero.</exception>
168         /// <exception cref="ArgumentOutOfRangeException">
169         ///     <paramref name="options"/> contain a invalid bit.<br/>
170         ///     -or-<br/>
171         ///     <paramref name="behaviors"/> contain a invalid bit.
172         /// </exception>
173         /// <exception cref="InvalidOperationException">The focus has already been acquired.</exception>
174         /// <exception cref="AudioPolicyException">Called in <see cref="FocusStateChanged"/> raised by releasing focus.</exception>
175         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
176         /// <since_tizen> 3 </since_tizen>
177         public void AcquireFocus(AudioStreamFocusOptions options, AudioStreamBehaviors behaviors, string extraInfo)
178         {
179             if (options == 0)
180             {
181                 throw new ArgumentException("options can't be zero.", nameof(options));
182             }
183
184             if (options.IsValid() == false)
185             {
186                 throw new ArgumentOutOfRangeException(nameof(options), options, "options contains a invalid bit.");
187             }
188
189             if (behaviors.IsValid() == false)
190             {
191                 throw new ArgumentOutOfRangeException(nameof(behaviors), behaviors, "behaviors contains a invalid bit.");
192             }
193
194             Interop.AudioStreamPolicy.AcquireFocus(Handle, options, behaviors, extraInfo).
195                 ThrowIfError("Failed to acquire focus");
196         }
197
198         /// <summary>
199         /// Releases the acquired focus.
200         /// </summary>
201         /// <param name="options">The focus mask that you want to release.</param>
202         /// <param name="behaviors">The requesting behaviors.</param>
203         /// <param name="extraInfo">The extra information for this request. This value can be null.</param>
204         /// <exception cref="ArgumentException"><paramref name="options"/> is zero.</exception>
205         /// <exception cref="ArgumentOutOfRangeException">
206         ///     <paramref name="options"/> contain a invalid bit.<br/>
207         ///     -or-<br/>
208         ///     <paramref name="behaviors"/> contain a invalid bit.
209         /// </exception>
210         /// <exception cref="InvalidOperationException">The focus has not been acquired.</exception>
211         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
212         /// <since_tizen> 3 </since_tizen>
213         public void ReleaseFocus(AudioStreamFocusOptions options, AudioStreamBehaviors behaviors, string extraInfo)
214         {
215             if (options == 0)
216             {
217                 throw new ArgumentException("options can't be zero.", nameof(options));
218             }
219
220             if (options.IsValid() == false)
221             {
222                 throw new ArgumentOutOfRangeException(nameof(options), options, "options contains a invalid bit.");
223             }
224
225             if (behaviors.IsValid() == false)
226             {
227                 throw new ArgumentOutOfRangeException(nameof(behaviors), behaviors, "behaviors contains a invalid bit.");
228             }
229
230             Interop.AudioStreamPolicy.ReleaseFocus(Handle, options, behaviors, extraInfo).
231                 ThrowIfError("Failed to release focus");
232         }
233
234         /// <summary>
235         /// Applies the stream routing.
236         /// </summary>
237         /// <remarks>
238         /// If the stream has not been made yet, this will be applied when the stream starts to play.
239         /// </remarks>
240         /// <seealso cref="AddDeviceForStreamRouting(AudioDevice)"/>
241         /// <seealso cref="RemoveDeviceForStreamRouting(AudioDevice)"/>
242         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
243         /// <since_tizen> 3 </since_tizen>
244         public void ApplyStreamRouting()
245         {
246             Interop.AudioStreamPolicy.ApplyStreamRouting(Handle).ThrowIfError("Failed to apply stream routing");
247         }
248
249         /// <summary>
250         /// Adds a device for the stream routing.
251         /// </summary>
252         /// <param name="device">The device to add.</param>
253         /// <remarks>
254         /// The available <see cref="AudioStreamType"/> is <see cref="AudioStreamType.Voip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
255         /// </remarks>
256         /// <exception cref="InvalidOperationException">
257         ///     The device is not connected.<br/>
258         ///     -or-<br/>
259         ///     An internal error occurs.
260         /// </exception>
261         /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
262         /// <exception cref="AudioPolicyException"><see cref="AudioStreamType"/> of <paramref name="device"/> is unavailable for this.</exception>
263         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
264         /// <seealso cref="AudioManager.GetConnectedDevices()"/>
265         /// <seealso cref="ApplyStreamRouting"/>
266         /// <since_tizen> 3 </since_tizen>
267         public void AddDeviceForStreamRouting(AudioDevice device)
268         {
269             if (device == null)
270             {
271                 throw new ArgumentNullException(nameof(device));
272             }
273
274             var ret = Interop.AudioStreamPolicy.AddDeviceForStreamRouting(Handle, device.Id);
275
276             if (ret == AudioManagerError.NoData)
277             {
278                 throw new InvalidOperationException("The device seems not connected.");
279             }
280
281             ret.ThrowIfError("Failed to add device for stream routing");
282         }
283
284         /// <summary>
285         /// Removes the device for the stream routing.
286         /// </summary>
287         /// <param name="device">The device to remove.</param>
288         /// <remarks>
289         /// The available <see cref="AudioStreamType"/> is <see cref="AudioStreamType.Voip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
290         /// </remarks>
291         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
292         /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
293         /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
294         /// <seealso cref="AudioManager.GetConnectedDevices()"/>
295         /// <since_tizen> 3 </since_tizen>
296         public void RemoveDeviceForStreamRouting(AudioDevice device)
297         {
298             if (device == null)
299             {
300                 throw new ArgumentNullException(nameof(device));
301             }
302
303             Interop.AudioStreamPolicy.RemoveDeviceForStreamRouting(Handle, device.Id).
304                 ThrowIfError("Failed to remove device for stream routing");
305         }
306
307         /// <summary>
308         /// Releases all resources used by the <see cref="AudioStreamPolicy"/>.
309         /// </summary>
310         /// <since_tizen> 3 </since_tizen>
311         public void Dispose()
312         {
313             Dispose(true);
314         }
315
316         /// <summary>
317         /// Releases the unmanaged resources used by the <see cref="AudioStreamPolicy"/>.
318         /// </summary>
319         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
320         /// <since_tizen> 3 </since_tizen>
321         protected virtual void Dispose(bool disposing)
322         {
323             if (!_disposed)
324             {
325                 if (_handle != null)
326                 {
327                     _handle.Dispose();
328                 }
329                 _disposed = true;
330             }
331         }
332
333         #region Static events
334
335         private static bool _isWatchCallbackRegistered;
336         private static EventHandler<StreamFocusStateChangedEventArgs> _streamFocusStateChanged;
337         private static Interop.AudioStreamPolicy.FocusStateWatchCallback _focusStateWatchCallback;
338         private static readonly object _streamFocusEventLock = new object();
339
340         /// <summary>
341         /// Occurs when the focus state for stream types is changed regardless of the process.
342         /// </summary>
343         /// <since_tizen> 3 </since_tizen>
344         public static event EventHandler<StreamFocusStateChangedEventArgs> StreamFocusStateChanged
345         {
346             add
347             {
348                 lock (_streamFocusEventLock)
349                 {
350                     if (_isWatchCallbackRegistered == false)
351                     {
352                         RegisterFocusStateWatch();
353                         _isWatchCallbackRegistered = true;
354                     }
355                     _streamFocusStateChanged += value;
356                 }
357             }
358             remove
359             {
360                 lock (_streamFocusEventLock)
361                 {
362                     _streamFocusStateChanged -= value;
363                 }
364             }
365         }
366
367         private static void RegisterFocusStateWatch()
368         {
369             _focusStateWatchCallback = (id, options, focusState, reason, extraInfo, _) =>
370             {
371                 _streamFocusStateChanged?.Invoke(null,
372                     new StreamFocusStateChangedEventArgs(options, focusState, reason, extraInfo));
373             };
374
375             Interop.AudioStreamPolicy.AddFocusStateWatchCallback(
376                 AudioStreamFocusOptions.Playback | AudioStreamFocusOptions.Recording,
377                 _focusStateWatchCallback, IntPtr.Zero, out var cbId).
378                 ThrowIfError("Failed to initialize focus state event");
379         }
380         #endregion
381     }
382 }