[AudioManager] Add properties for the preferred device (#987)
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia / AudioManager / AudioStreamPolicy.cs
old mode 100755 (executable)
new mode 100644 (file)
index f82dfb4..5a50f73
@@ -20,36 +20,43 @@ using System.Diagnostics;
 namespace Tizen.Multimedia
 {
     /// <summary>
-    /// Provides the ability to control a sound stream.
+    /// Provides the ability to control the sound stream.
     /// </summary>
+    /// <since_tizen> 3 </since_tizen>
     public class AudioStreamPolicy : IDisposable
     {
         private AudioStreamPolicyHandle _handle;
         private bool _disposed = false;
         private Interop.AudioStreamPolicy.FocusStateChangedCallback _focusStateChangedCallback;
+        private static AudioDevice _inputDevice = null;
+        private static AudioDevice _outputDevice = null;
+        private const string Tag = "Tizen.Multimedia.AudioStreamPolicy";
 
         /// <summary>
-        ///  Initializes a new instance of the <see cref="AudioStreamPolicy"/> class with <see cref="AudioStreamType"/>
+        /// Initializes a new instance of the <see cref="AudioStreamPolicy"/> class with <see cref="AudioStreamType"/>.
         /// </summary>
         /// <remarks>
         /// To apply the stream policy according to this stream information, the AudioStreamPolicy should
-        /// be passed to other APIs related to playback or recording. (e.g., <see cref="Player"/>, <see cref="WavPlayer"/> , etc.)
+        /// be passed to other APIs related to playback or recording. (For example., <see cref="T:Tizen.Multimedia.Player"/>,
+        /// <see cref="T:Tizen.Multimedia.WavPlayer"/> , etc.)
         /// </remarks>
-        /// <param name="streamType">Type of sound stream for which policy needs to be created.</param>
+        /// <param name="streamType">The type of the sound stream for which the policy needs to be created.</param>
+        /// <exception cref="ArgumentException"><paramref name="streamType"/> is invalid.</exception>
+        /// <since_tizen> 3 </since_tizen>
         public AudioStreamPolicy(AudioStreamType streamType)
         {
             ValidationUtil.ValidateEnum(typeof(AudioStreamType), streamType, nameof(streamType));
 
             _focusStateChangedCallback = (IntPtr streamInfo, AudioStreamFocusOptions focusMask,
                 AudioStreamFocusState state, AudioStreamFocusChangedReason reason, AudioStreamBehaviors behaviors,
-                string extraInfo, IntPtr userData) =>
+                string extraInfo, IntPtr _) =>
             {
                 FocusStateChanged?.Invoke(this,
                     new AudioStreamPolicyFocusStateChangedEventArgs(focusMask, state, reason, behaviors, extraInfo));
             };
 
             Interop.AudioStreamPolicy.Create(streamType, _focusStateChangedCallback,
-                IntPtr.Zero, out _handle).Validate("Unable to create stream information");
+                IntPtr.Zero, out _handle).ThrowIfError("Unable to create stream information");
 
             Debug.Assert(_handle != null);
         }
@@ -60,6 +67,7 @@ namespace Tizen.Multimedia
         /// <remarks>
         /// The event is raised in the internal thread.
         /// </remarks>
+        /// <since_tizen> 4 </since_tizen>
         public event EventHandler<AudioStreamPolicyFocusStateChangedEventArgs> FocusStateChanged;
 
         /// <summary>
@@ -70,18 +78,19 @@ namespace Tizen.Multimedia
         /// it returns <see cref="AudioVolumeType.None"/>.
         /// </remarks>
         /// <value>The <see cref="AudioVolumeType"/> of the policy instance.</value>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <since_tizen> 3 </since_tizen>
         public AudioVolumeType VolumeType
         {
             get
             {
-                AudioVolumeType type;
-                var ret = Interop.AudioStreamPolicy.GetSoundType(Handle, out type);
+                var ret = Interop.AudioStreamPolicy.GetSoundType(Handle, out var type);
                 if (ret == AudioManagerError.NoData)
                 {
                     return AudioVolumeType.None;
                 }
 
-                ret.Validate("Failed to get volume type");
+                ret.ThrowIfError("Failed to get volume type");
 
                 return type;
             }
@@ -96,41 +105,47 @@ namespace Tizen.Multimedia
         }
 
         /// <summary>
-        /// Gets the state of focus for playback.
+        /// Gets the state of focus for the playback.
         /// </summary>
         /// <value>The state of focus for playback.</value>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <since_tizen> 3 </since_tizen>
         public AudioStreamFocusState PlaybackFocusState => GetFocusState(true);
 
         /// <summary>
-        /// Gets the state of focus for recording.
+        /// Gets the state of focus for the recording.
         /// </summary>
         /// <value>The state of focus for recording.</value>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <since_tizen> 3 </since_tizen>
         public AudioStreamFocusState RecordingFocusState => GetFocusState(false);
 
         /// <summary>
         /// Gets or sets the auto focus reacquisition.
         /// </summary>
         /// <value>
-        /// true if the auto focus reacquisition is enabled; otherwise, false.\n
+        /// true if the auto focus reacquisition is enabled; otherwise, false.<br/>
         /// The default is true.
         /// </value>
         /// <remarks>
         /// If you don't want to reacquire the focus you've lost automatically,
         /// disable the focus reacquisition.
         /// </remarks>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <since_tizen> 3 </since_tizen>
         public bool FocusReacquisitionEnabled
         {
             get
             {
                 Interop.AudioStreamPolicy.GetFocusReacquisition(Handle, out var enabled).
-                    Validate("Failed to get focus reacquisition state");
+                    ThrowIfError("Failed to get focus reacquisition state");
 
                 return enabled;
             }
             set
             {
                 Interop.AudioStreamPolicy.SetFocusReacquisition(Handle, value).
-                    Validate("Failed to set focus reacquisition");
+                    ThrowIfError("Failed to set focus reacquisition");
             }
         }
 
@@ -152,6 +167,16 @@ namespace Tizen.Multimedia
         /// <param name="options">The focuses that you want to acquire.</param>
         /// <param name="behaviors">The requesting behaviors.</param>
         /// <param name="extraInfo">The extra information for this request. This value can be null.</param>
+        /// <exception cref="ArgumentException"><paramref name="options"/> is zero.</exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///     <paramref name="options"/> contain a invalid bit.<br/>
+        ///     -or-<br/>
+        ///     <paramref name="behaviors"/> contain a invalid bit.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">The focus has already been acquired.</exception>
+        /// <exception cref="AudioPolicyException">Called in <see cref="FocusStateChanged"/> raised by releasing focus.</exception>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <since_tizen> 3 </since_tizen>
         public void AcquireFocus(AudioStreamFocusOptions options, AudioStreamBehaviors behaviors, string extraInfo)
         {
             if (options == 0)
@@ -170,7 +195,7 @@ namespace Tizen.Multimedia
             }
 
             Interop.AudioStreamPolicy.AcquireFocus(Handle, options, behaviors, extraInfo).
-                Validate("Failed to acquire focus");
+                ThrowIfError("Failed to acquire focus");
         }
 
         /// <summary>
@@ -179,6 +204,15 @@ namespace Tizen.Multimedia
         /// <param name="options">The focus mask that you want to release.</param>
         /// <param name="behaviors">The requesting behaviors.</param>
         /// <param name="extraInfo">The extra information for this request. This value can be null.</param>
+        /// <exception cref="ArgumentException"><paramref name="options"/> is zero.</exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///     <paramref name="options"/> contain a invalid bit.<br/>
+        ///     -or-<br/>
+        ///     <paramref name="behaviors"/> contain a invalid bit.
+        /// </exception>
+        /// <exception cref="InvalidOperationException">The focus has not been acquired.</exception>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <since_tizen> 3 </since_tizen>
         public void ReleaseFocus(AudioStreamFocusOptions options, AudioStreamBehaviors behaviors, string extraInfo)
         {
             if (options == 0)
@@ -197,7 +231,7 @@ namespace Tizen.Multimedia
             }
 
             Interop.AudioStreamPolicy.ReleaseFocus(Handle, options, behaviors, extraInfo).
-                Validate("Failed to release focus");
+                ThrowIfError("Failed to release focus");
         }
 
         /// <summary>
@@ -208,9 +242,11 @@ namespace Tizen.Multimedia
         /// </remarks>
         /// <seealso cref="AddDeviceForStreamRouting(AudioDevice)"/>
         /// <seealso cref="RemoveDeviceForStreamRouting(AudioDevice)"/>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <since_tizen> 3 </since_tizen>
         public void ApplyStreamRouting()
         {
-            Interop.AudioStreamPolicy.ApplyStreamRouting(Handle).Validate("Failed to apply stream routing");
+            Interop.AudioStreamPolicy.ApplyStreamRouting(Handle).ThrowIfError("Failed to apply stream routing");
         }
 
         /// <summary>
@@ -220,22 +256,32 @@ namespace Tizen.Multimedia
         /// <remarks>
         /// The available <see cref="AudioStreamType"/> is <see cref="AudioStreamType.Voip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
         /// </remarks>
+        /// <exception cref="InvalidOperationException">
+        ///     The device is not connected.<br/>
+        ///     -or-<br/>
+        ///     An internal error occurs.
+        /// </exception>
+        /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
+        /// <exception cref="AudioPolicyException"><see cref="AudioStreamType"/> of <paramref name="device"/> is unavailable for this.</exception>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
         /// <seealso cref="AudioManager.GetConnectedDevices()"/>
         /// <seealso cref="ApplyStreamRouting"/>
+        /// <since_tizen> 3 </since_tizen>
         public void AddDeviceForStreamRouting(AudioDevice device)
         {
             if (device == null)
             {
                 throw new ArgumentNullException(nameof(device));
             }
+
             var ret = Interop.AudioStreamPolicy.AddDeviceForStreamRouting(Handle, device.Id);
 
             if (ret == AudioManagerError.NoData)
             {
-                throw new ArgumentException("The device seems not connected.", nameof(device));
+                throw new InvalidOperationException("The device seems not connected.");
             }
 
-            ret.Validate("Failed to add device for stream routing");
+            ret.ThrowIfError("Failed to add device for stream routing");
         }
 
         /// <summary>
@@ -245,6 +291,11 @@ namespace Tizen.Multimedia
         /// <remarks>
         /// The available <see cref="AudioStreamType"/> is <see cref="AudioStreamType.Voip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
         /// </remarks>
+        /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
+        /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <seealso cref="AudioManager.GetConnectedDevices()"/>
+        /// <since_tizen> 3 </since_tizen>
         public void RemoveDeviceForStreamRouting(AudioDevice device)
         {
             if (device == null)
@@ -253,24 +304,141 @@ namespace Tizen.Multimedia
             }
 
             Interop.AudioStreamPolicy.RemoveDeviceForStreamRouting(Handle, device.Id).
-                Validate("Failed to remove device for stream routing");
+                ThrowIfError("Failed to remove device for stream routing");
+        }
+
+        /// <summary>
+        /// Gets or sets the preferred input device.
+        /// </summary>
+        /// <value>
+        /// The <see cref="AudioDevice"/> instance.<br/>
+        /// The default is null which means any device is not set on this property.
+        /// </value>
+        /// <remarks>
+        /// This property is to set a specific built-in device when the system has multiple devices of the same built-in device type.
+        /// When there's only one device for a built-in device type in the system, nothing will happen even if this property is set successfully.
+        /// </remarks>
+        /// <exception cref="ArgumentException">A device is not for input.</exception>
+        /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
+        /// <exception cref="AudioPolicyException">A device is not supported by this <see cref="AudioStreamPolicy"/> instance.</exception>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <seealso cref="AudioManager.GetConnectedDevices()"/>
+        /// <since_tizen> 6 </since_tizen>
+        public AudioDevice PreferredInputDevice
+        {
+            get
+            {
+                /* This P/Invoke intends to validate if the core audio system
+                 * is normal. Otherwise, it'll throw an error here. */
+                Interop.AudioStreamPolicy.GetPreferredDevice(Handle, out var inDeviceId, out _).
+                    ThrowIfError("Failed to get preferred input device");
+
+                Log.Debug(Tag, $"preferred input device id:{inDeviceId}");
+
+                return _inputDevice;
+            }
+            set
+            {
+                Interop.AudioStreamPolicy.SetPreferredDevice(Handle, AudioDeviceIoDirection.Input, value?.Id ?? 0).
+                    ThrowIfError("Failed to set preferred input device");
+
+                _inputDevice = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the preferred output device.
+        /// </summary>
+        /// <value>
+        /// The <see cref="AudioDevice"/> instance.<br/>
+        /// The default is null which means any device is not set on this property.
+        /// </value>
+        /// <remarks>
+        /// This property is to set a specific built-in device when the system has multiple devices of the same built-in device type.
+        /// When there's only one device for a built-in device type in the system, nothing will happen even if this property is set successfully.
+        /// </remarks>
+        /// <exception cref="ArgumentException">A device is not for output.</exception>
+        /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
+        /// <exception cref="AudioPolicyException">A device is not supported by this <see cref="AudioStreamPolicy"/> instance.</exception>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <seealso cref="AudioManager.GetConnectedDevices()"/>
+        /// <since_tizen> 6 </since_tizen>
+        public AudioDevice PreferredOutputDevice
+        {
+            get
+            {
+                /* This P/Invoke intends to validate if the core audio system
+                 * is normal. Otherwise, it'll throw an error here. */
+                Interop.AudioStreamPolicy.GetPreferredDevice(Handle, out _, out var outDeviceId).
+                    ThrowIfError("Failed to get preferred output device");
+
+                Log.Debug(Tag, $"preferred output device id:{outDeviceId}");
+
+                return _outputDevice;
+            }
+            set
+            {
+                Interop.AudioStreamPolicy.SetPreferredDevice(Handle, AudioDeviceIoDirection.Output, value?.Id ?? 0).
+                    ThrowIfError("Failed to set preferred output device");
+
+                _outputDevice = value;
+            }
+        }
+
+        /// <summary>
+        /// Checks if any stream from the current AudioStreamPolicy is using the device.
+        /// </summary>
+        /// <returns>true if any audio stream from the current AudioStreamPolicy is using the device; otherwise, false.</returns>
+        /// <param name="device">The device to be checked.</param>
+        /// <remarks>
+        /// The AudioStreamPolicy can be applied to each playback or recording stream via other API set.
+        /// (For example., <see cref="T:Tizen.Multimedia.Player"/>, <see cref="T:Tizen.Multimedia.WavPlayer"/>,
+        /// <see cref="T:Tizen.Multimedia.AudioPlayback"/>, <see cref="T:Tizen.Multimedia.AudioCapture"/>, etc.)
+        /// This method returns true only when the device is used for the stream which meets to the two conditions.
+        /// One is that the current AudioStreamPolicy sets a audio route path to the device and the other is that the playback
+        /// or recording stream from other API set should have already started to prepare or to play.(It depends on the API set.)
+        /// </remarks>
+        /// <exception cref="ArgumentNullException"><paramref name="device"/> is null.</exception>
+        /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
+        /// <exception cref="ObjectDisposedException">The <see cref="AudioStreamPolicy"/> has already been disposed of.</exception>
+        /// <seealso cref="AudioManager.GetConnectedDevices()"/>
+        /// <since_tizen> 6 </since_tizen>
+        public bool HasStreamOnDevice(AudioDevice device)
+        {
+            if (device == null)
+            {
+                throw new ArgumentNullException(nameof(device));
+            }
+
+            var ret = Interop.AudioStreamPolicy.IsStreamOnDevice(Handle, device.Id, out var isOn);
+            ret.ThrowIfError("Failed to check stream on device");
+
+            return isOn;
         }
 
         /// <summary>
         /// Releases all resources used by the <see cref="AudioStreamPolicy"/>.
         /// </summary>
+        /// <since_tizen> 3 </since_tizen>
         public void Dispose()
         {
             Dispose(true);
+            GC.SuppressFinalize(this);
         }
 
         /// <summary>
         /// Releases the unmanaged resources used by the <see cref="AudioStreamPolicy"/>.
         /// </summary>
         /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
+        /// <since_tizen> 3 </since_tizen>
         protected virtual void Dispose(bool disposing)
         {
-            if (!_disposed)
+            if (_disposed)
+            {
+                return;
+            }
+
+            if (disposing)
             {
                 if (_handle != null)
                 {
@@ -285,11 +453,12 @@ namespace Tizen.Multimedia
         private static bool _isWatchCallbackRegistered;
         private static EventHandler<StreamFocusStateChangedEventArgs> _streamFocusStateChanged;
         private static Interop.AudioStreamPolicy.FocusStateWatchCallback _focusStateWatchCallback;
-        private static object _streamFocusEventLock = new object();
+        private static readonly object _streamFocusEventLock = new object();
 
         /// <summary>
         /// Occurs when the focus state for stream types is changed regardless of the process.
         /// </summary>
+        /// <since_tizen> 3 </since_tizen>
         public static event EventHandler<StreamFocusStateChangedEventArgs> StreamFocusStateChanged
         {
             add
@@ -315,8 +484,7 @@ namespace Tizen.Multimedia
 
         private static void RegisterFocusStateWatch()
         {
-            _focusStateWatchCallback = (int id, AudioStreamFocusOptions options, AudioStreamFocusState focusState,
-                AudioStreamFocusChangedReason reason, string extraInfo, IntPtr userData) =>
+            _focusStateWatchCallback = (id, options, focusState, reason, extraInfo, _) =>
             {
                 _streamFocusStateChanged?.Invoke(null,
                     new StreamFocusStateChangedEventArgs(options, focusState, reason, extraInfo));
@@ -325,7 +493,7 @@ namespace Tizen.Multimedia
             Interop.AudioStreamPolicy.AddFocusStateWatchCallback(
                 AudioStreamFocusOptions.Playback | AudioStreamFocusOptions.Recording,
                 _focusStateWatchCallback, IntPtr.Zero, out var cbId).
-                Validate("Failed to initialize focus state event");
+                ThrowIfError("Failed to initialize focus state event");
         }
         #endregion
     }