Implement libsessiond wrapper
authoradaszkiewicza <ada.antek@gmail.com>
Thu, 20 Apr 2023 11:12:08 +0000 (13:12 +0200)
committerChanwoo Choi <chanwoo@kernel.org>
Mon, 15 May 2023 02:58:09 +0000 (11:58 +0900)
packaging/csapi-tizenfx.spec
packaging/csapi-tizenfx.spec.in
src/Tizen.System.Session/Interop/Interop.Libraries.cs [new file with mode: 0644]
src/Tizen.System.Session/Interop/Interop.Session.cs [new file with mode: 0644]
src/Tizen.System.Session/Session/Session.cs [new file with mode: 0644]
src/Tizen.System.Session/Session/SessionEnums.cs [new file with mode: 0644]
src/Tizen.System.Session/Session/SessionErrorFactory.cs [new file with mode: 0644]
src/Tizen.System.Session/Session/SessionEventArgs.cs [new file with mode: 0644]
src/Tizen.System.Session/Session/SessionStructs.cs [new file with mode: 0644]
src/Tizen.System.Session/Tizen.System.Session.csproj [new file with mode: 0644]

index f75ed15..1d507a9 100644 (file)
@@ -57,6 +57,7 @@ BuildRequires: pkgconfig(capi-ui-inputmethod)
 BuildRequires: pkgconfig(stt-engine)
 BuildRequires: pkgconfig(tts-engine)
 BuildRequires: pkgconfig(chromium-efl)
+BuildRequires: pkgconfig(libsessiond)
 %if "%{profile}" == "tv"
 BuildRequires: pkgconfig(trustzone-nwd)
 %else
index afa487e..f0e2c52 100644 (file)
@@ -56,6 +56,7 @@ BuildRequires: pkgconfig(capi-ui-inputmethod)
 BuildRequires: pkgconfig(stt-engine)
 BuildRequires: pkgconfig(tts-engine)
 BuildRequires: pkgconfig(chromium-efl)
+BuildRequires: pkgconfig(libsessiond)
 %if "%{profile}" == "tv"
 BuildRequires: pkgconfig(trustzone-nwd)
 %else
diff --git a/src/Tizen.System.Session/Interop/Interop.Libraries.cs b/src/Tizen.System.Session/Interop/Interop.Libraries.cs
new file mode 100644 (file)
index 0000000..c65f3d7
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+* Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the License);
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an AS IS BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+internal static partial class Interop
+{
+    internal static partial class Libraries
+    {
+        internal const string Session = "libsessiond.so.0";
+
+        internal const string Libc = "libc.so.6";
+    }
+}
diff --git a/src/Tizen.System.Session/Interop/Interop.Session.cs b/src/Tizen.System.Session/Interop/Interop.Session.cs
new file mode 100644 (file)
index 0000000..8c15884
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+* Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the License);
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an AS IS BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Text;
+using Tizen.System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class Session
+    {
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        public delegate void SubsessionReplyCallback(int result, IntPtr cb_data);
+
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        public delegate void SubsessionEventCallback(SubsessionEventInfoNative info, IntPtr cb_data);
+
+        [DllImport(Libraries.Session, EntryPoint = "subsession_add_user", CallingConvention = CallingConvention.Cdecl)]
+        public static extern SessionError SubsessionAddUser(int session_uid, string user, SubsessionReplyCallback cb, IntPtr data);
+
+        [DllImport(Libraries.Session, EntryPoint = "subsession_remove_user", CallingConvention = CallingConvention.Cdecl)]
+        public static extern SessionError SubsessionRemoveUser(int session_uid, string user, SubsessionReplyCallback cb, IntPtr data);
+
+        [DllImport(Libraries.Session, EntryPoint = "subsession_switch_user", CallingConvention = CallingConvention.Cdecl)]
+        public static extern SessionError SubsessionSwitchUser(int session_uid, string next_user, SubsessionReplyCallback cb, IntPtr data);
+
+        [DllImport(Libraries.Session, EntryPoint = "subsession_get_user_list", CallingConvention = CallingConvention.Cdecl)]
+        public static extern SessionError SubsessionGetUserList(int session_uid, out IntPtr list, out int user_count);
+
+        [DllImport(Libraries.Session, EntryPoint = "subsession_free_user_list", CallingConvention = CallingConvention.Cdecl)]
+        public static extern void SubsessionFreeUserList(IntPtr list);
+
+        [DllImport(Libraries.Session, EntryPoint = "subsession_get_current_user", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+        public static extern SessionError SubsessionGetCurrentUser(int session_uid, StringBuilder user);
+
+        [DllImport(Libraries.Session, EntryPoint = "subsession_register_event_callback", CallingConvention = CallingConvention.Cdecl)]
+        public static extern SessionError SubesssionRegisterEventCallback(int session_uid, SessionEventType event_bits, SubsessionEventCallback cb, IntPtr data);
+
+        [DllImport(Libraries.Session, EntryPoint = "subsession_unregister_event_callback", CallingConvention = CallingConvention.Cdecl)]
+        public static extern SessionError SubesssionUnregisterEventCallback(int session_uid, SessionEventType event_bits);
+
+        [DllImport(Libraries.Session, EntryPoint = "subsession_event_wait_done", CallingConvention = CallingConvention.Cdecl)]
+        public static extern SessionError SubsessionEventWaitDone(SubsessionEventInfoNative info);
+
+        [DllImport(Libraries.Libc, EntryPoint = "getuid", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int GetUID();
+    }
+}
diff --git a/src/Tizen.System.Session/Session/Session.cs b/src/Tizen.System.Session/Session/Session.cs
new file mode 100644 (file)
index 0000000..6303e78
--- /dev/null
@@ -0,0 +1,509 @@
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tizen.System
+{
+    /// <summary>
+    /// Provides methods to manage subsession users. Allows to register for events triggered by operations on subsession users.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public sealed class Session
+    {
+        /// <summary>
+        /// UID of current system user.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static readonly int CurrentUID;
+
+        /// <summary>
+        /// Maximum length of any given user ID.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public const int MaxUserLength = 20;
+
+        /// <summary>
+        /// Special subsession ID, which is always present and does not represent any user.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public const string EmptyUser = "";
+
+        private static ConcurrentDictionary<int, Session> s_sessionInstances = new ConcurrentDictionary<int, Session>();
+
+        private readonly object _replyLock = new object();
+
+        private IDictionary<int, Interop.Session.SubsessionReplyCallback> _replyMap = new ConcurrentDictionary<int, Interop.Session.SubsessionReplyCallback>();
+
+        private int _replyID = 0;
+
+        private delegate void EventDelegate(SubsessionEventInfoNative infoNative, IntPtr data);
+
+        static Session()
+        {
+            CurrentUID = Interop.Session.GetUID();
+        }
+
+        private Session(int sessionUID)
+        {
+            SessionUID = sessionUID;
+        }
+
+        /// <summary>
+        /// Gets a Session object instance for a given sessionUID.
+        /// </summary>
+        /// <param name="sessionUID">Session UID of a requested Session object instance</param>
+        /// <returns>Returns requested Session object</returns>
+        /// <remarks>
+        /// To ensure thread safety, expilicit creation of Session object is not allowed.
+        /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static Session GetInstance(int sessionUID)
+        {
+            s_sessionInstances.TryAdd(sessionUID, new Session(sessionUID));
+            return s_sessionInstances[sessionUID];
+        }
+
+        /// <summary>
+        /// Gets session UID of this session object.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public int SessionUID { get; private set; }
+
+        /// <summary>
+        /// Gets a list of all availible subsession user IDs for this session.
+        /// </summary>
+        /// <remarks>
+        /// The list of users depends on whether the session UID for this session object exists or not. If it
+        /// doesn't, the user list is empty (in particular this is not an error).
+        /// However if the session UID exists, the user list will contain the subsession
+        /// IDs (if they exist), but also the default value, which is "" (empty string, see EmptyUser field).
+        /// This doesn't mean that "" is a subsession ID in the same way as others; it is just a marker meaning that no subsession is
+        /// enabled.
+        /// </remarks>
+        /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
+        /// <exception cref="OutOfMemoryException">Out of memory</exception>
+        /// <exception cref="IOException">Internal error</exception>
+        /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
+        /// <exception cref="NotSupportedException">Not supported</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public IReadOnlyList<string> GetUsers()
+        {
+
+            IntPtr ptr;
+            int count;
+
+            SessionError ret = Interop.Session.SubsessionGetUserList(SessionUID, out ptr, out count);
+            CheckError(ret, "Interop failed to get user list");
+
+            string[] users;
+            IntPtrToStringArray(ptr, count, out users);
+            Interop.Session.SubsessionFreeUserList(ptr);
+
+            return new List<string>(users);
+        }
+
+        /// <summary>
+        /// Gets a currently active subession user ID for this session.
+        /// </summary>
+        /// <remarks>
+        /// When no subsession is enabled, "" (empty string, see EmptyUser field) is returned.
+        /// This doesn't mean that "" is a subsession ID in the same way as others; it is just a marker meaning
+        /// that no subsession is enabled.
+        /// </remarks>
+        /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
+        /// <exception cref="OutOfMemoryException">Out of memory</exception>
+        /// <exception cref="IOException">Internal error</exception>
+        /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
+        /// <exception cref="NotSupportedException">Not supported</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string GetCurrentUser()
+        {
+            StringBuilder user = new StringBuilder(MaxUserLength);
+            SessionError ret = Interop.Session.SubsessionGetCurrentUser(SessionUID, user);
+            CheckError(ret, "Interop failed to get current subsession user");
+
+            return user.ToString();
+        }
+
+        /// <summary>
+        /// Request new subsession to be created.
+        /// </summary>
+        /// <param name="userName">Subesssion user ID to be created</param>
+        /// <remarks>
+        /// Subsession ID must not start with a dot or have slashes.
+        /// </remarks>
+        /// <exception cref="ArgumentException">Session UID of this object is invalid, or user ID is not a valid subession ID</exception>
+        /// <exception cref="InvalidOperationException"> Provided subsession user ID already exists</exception>
+        /// <exception cref="OutOfMemoryException">Out of memory</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Task SubsessionAddUserAsync(string userName)
+        {
+            var task = new TaskCompletionSource<bool>();
+            int taskID = 0;
+
+            lock (_replyLock)
+            {
+                taskID = _replyID++;
+            }
+
+            _replyMap[taskID] = (int result, IntPtr data) =>
+            {
+                _replyMap.Remove((int)data);
+                try
+                {
+                    CheckError((SessionError)result, "Interop failed to complete adding a new subsession user");
+                }
+                catch (Exception exception)
+                {
+                    task.SetException(exception);
+                    return;
+                }
+
+                task.SetResult(true);
+            };
+
+            SessionError ret = Interop.Session.SubsessionAddUser(SessionUID, userName, _replyMap[taskID], (IntPtr)taskID);
+            CheckError(ret, "Interop failed to register a reply for adding a user");
+            return task.Task;
+        }
+
+        /// <summary>
+        /// Request an existing subsession to be removed.
+        /// </summary>
+        /// <param name="userName">Existing subesssion user ID to be removed</param>
+        /// <remarks>
+        /// Subsession ID must not start with a dot or have slashes.
+        /// Only inactive session ID can be removed. In order remove currently used session ID first switch to special
+        /// session ID "" (empty string, see EmptyUser), and only after switch completes, remove previously active session ID.
+        /// </remarks>
+        /// <exception cref="ArgumentException">Session UID of this object is invalid, or user ID is not a valid subession ID</exception>
+        /// <exception cref="InvalidOperationException">Provided subsession user ID does not exist</exception>
+        /// <exception cref="OutOfMemoryException">Out of memory</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Task SubsessionRemoveUserAsync(string userName)
+        {
+            var task = new TaskCompletionSource<bool>();
+            int taskID = 0;
+
+            lock (_replyLock)
+            {
+                taskID = _replyID++;
+            }
+
+            _replyMap[taskID] = (int result, IntPtr data) =>
+            {
+                _replyMap.Remove((int)data);
+
+                try
+                {
+                    CheckError((SessionError)result, "Interop failed to remove a subsession user");
+                }
+                catch (Exception exception)
+                {
+                    task.SetException(exception);
+                    return;
+                }
+
+                task.SetResult(true);
+            };
+
+            SessionError ret = Interop.Session.SubsessionRemoveUser(SessionUID, userName, _replyMap[taskID], (IntPtr)taskID);
+            CheckError(ret, "Interop failed to register a reply for removing a user");
+            return task.Task;
+        }
+
+        /// <summary>
+        /// Request a subession to become currently active.
+        /// </summary>
+        /// <param name="userName">Existing subesssion user ID to be set as active</param>
+        /// <remarks>
+        /// Subsession ID must not start with a dot or have slashes.
+        /// Special subsession ID "" (empty string, see EmptyUser) can be switched to, when it's required to deactivate
+        /// current subsession (this step is needed when current session is to be removed).
+        /// </remarks>
+        /// <exception cref="ArgumentException">Session UID of this object is invalid, or user ID is not a valid subession ID</exception>
+        /// <exception cref="InvalidOperationException">Provided subsession user ID to switch to does not exist</exception>
+        /// <exception cref="OutOfMemoryException">Out of memory</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Task SubsessionSwitchUserAsync(string userName)
+        {
+            var task = new TaskCompletionSource<bool>();
+            int taskID = 0;
+
+            lock (_replyLock)
+            {
+                taskID = _replyID++;
+            }
+
+            _replyMap[taskID] = (int result, IntPtr data) =>
+            {
+                _replyMap.Remove((int)data);
+
+                try
+                {
+                    CheckError((SessionError)result, "Interop failed to switch to a different subsession user");
+                }
+                catch (Exception exception)
+                {
+                    task.SetException(exception);
+                    return;
+                }
+
+                task.SetResult(true);
+            };
+
+            SessionError ret = Interop.Session.SubsessionSwitchUser(SessionUID, userName, _replyMap[taskID], (IntPtr)taskID);
+            CheckError(ret, "Interop failed to register a reply for switching a user");
+            return task.Task;
+        }
+
+        /// <summary>
+        /// Mark event as completed.
+        /// </summary>
+        /// <param name="subsessionEventArgs">Event argument of the event (obtained from said event)</param>
+        /// <remarks>
+        /// This method is assumed to be called from an event handler. You can only mark an event as completed
+        /// if you registered for in in the same process.
+        /// </remarks>
+        /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
+        /// <exception cref="IOException">Internal error</exception>
+        /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
+        /// <exception cref="NotSupportedException">Not supported</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void SubsessionEventMarkAsDone(SubsessionEventArgs subsessionEventArgs)
+        {
+            SessionError ret = Interop.Session.SubsessionEventWaitDone(subsessionEventArgs.SessionInfo);
+            CheckError(ret, $"Interop failed to mark this client's event (of type {subsessionEventArgs.SessionInfo.eventType}) as finished");
+        }
+
+        private void OnAddUserWait(SubsessionEventInfoNative infoNative, IntPtr data)
+        {
+            _addUserWaitHandler?.Invoke(this, new AddUserEventArgs(infoNative));
+        }
+
+        private Interop.Session.SubsessionEventCallback _addUserWaitCB = null;
+
+        private event EventHandler<AddUserEventArgs> _addUserWaitHandler = null;
+
+        private readonly object _addUserWaitLock = new object();
+
+        /// <summary>
+        /// Event to be invoked when a new subession user is successfully added to this session.
+        /// </summary>
+        /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
+        /// <exception cref="OutOfMemoryException">Out of memory</exception>
+        /// <exception cref="IOException">Internal error</exception>
+        /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
+        /// <exception cref="NotSupportedException">Not supported</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<AddUserEventArgs> AddUserWait
+        {
+            add
+            {
+                lock (_addUserWaitLock)
+                {
+                    if (_addUserWaitHandler == null)
+                        RegisterCallbackForEvent(SessionEventType.AddUserWait, _addUserWaitCB, OnAddUserWait);
+                    _addUserWaitHandler += value;
+                }
+            }
+            remove
+            {
+                lock (_addUserWaitLock)
+                {
+                    _addUserWaitHandler -= value;
+                    if (_addUserWaitHandler == null)
+                        UnregisterCallbackForEvent(SessionEventType.AddUserWait, _addUserWaitCB);
+                }
+            }
+        }
+
+        private void OnRemoveUserWait(SubsessionEventInfoNative infoNative, IntPtr data)
+        {
+            _removeUserWaitHandler?.Invoke(this, new RemoveUserEventArgs(infoNative));
+        }
+
+        private Interop.Session.SubsessionEventCallback _removeUserWaitCB = null;
+
+        private event EventHandler<RemoveUserEventArgs> _removeUserWaitHandler = null;
+
+        private readonly object _removeUserWaitLock = new object();
+
+        /// <summary>
+        /// Event to be invoked when a subession user is successfully removed from this session.
+        /// </summary>
+        /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
+        /// <exception cref="OutOfMemoryException">Out of memory</exception>
+        /// <exception cref="IOException">Internal error</exception>
+        /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
+        /// <exception cref="NotSupportedException">Not supported</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<RemoveUserEventArgs> RemoveUserWait
+        {
+            add
+            {
+                lock (_removeUserWaitLock)
+                {
+                    if (_removeUserWaitHandler == null)
+                        RegisterCallbackForEvent(SessionEventType.RemoveUserWait, _removeUserWaitCB, OnRemoveUserWait);
+                    _removeUserWaitHandler += value;
+                }
+            }
+            remove
+            {
+                lock (_removeUserWaitLock)
+                {
+                    _removeUserWaitHandler -= value;
+                    if (_removeUserWaitHandler == null)
+                        UnregisterCallbackForEvent(SessionEventType.RemoveUserWait, _removeUserWaitCB);
+                }
+            }
+        }
+
+        private void OnSwitchUserWait(SubsessionEventInfoNative infoNative, IntPtr data)
+        {
+            _switchUserWaitHandler?.Invoke(this, new SwitchUserWaitEventArgs(infoNative));
+        }
+
+        private Interop.Session.SubsessionEventCallback _switchUserWaitCB = null;
+
+        private event EventHandler<SwitchUserWaitEventArgs> _switchUserWaitHandler = null;
+
+        private readonly object _switchUserWaitLock = new object();
+
+        /// <summary>
+        /// Event to be invoked when an existing subession user has begun switching to an active state.
+        /// </summary>
+        /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
+        /// <exception cref="OutOfMemoryException">Out of memory</exception>
+        /// <exception cref="IOException">Internal error</exception>
+        /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
+        /// <exception cref="NotSupportedException">Not supported</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<SwitchUserWaitEventArgs> SwitchUserWait
+        {
+            add
+            {
+                lock (_switchUserWaitLock)
+                {
+                    if (_switchUserWaitHandler == null)
+                        RegisterCallbackForEvent(SessionEventType.SwitchUserWait, _switchUserWaitCB, OnSwitchUserWait);
+                    _switchUserWaitHandler += value;
+                }
+            }
+            remove
+            {
+                lock (_switchUserWaitLock)
+                {
+                    _switchUserWaitHandler -= value;
+                    if (_switchUserWaitHandler == null)
+                        UnregisterCallbackForEvent(SessionEventType.SwitchUserWait, _switchUserWaitCB);
+                }
+            }
+        }
+
+        private void OnSwitchUserCompletion(SubsessionEventInfoNative infoNative, IntPtr data)
+        {
+            _switchUserCompletionHandler?.Invoke(this, new SwitchUserCompletionEventArgs(infoNative));
+        }
+
+        private Interop.Session.SubsessionEventCallback _switchUserCompletionCB = null;
+
+        private event EventHandler<SwitchUserCompletionEventArgs> _switchUserCompletionHandler = null;
+
+        private readonly object _switchUserCompletionLock = new object();
+
+        /// <summary>
+        /// Event to be invoked when an existing subession user is successfully switched to an acvite state.
+        /// </summary>
+        /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
+        /// <exception cref="OutOfMemoryException">Out of memory</exception>
+        /// <exception cref="IOException">Internal error</exception>
+        /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
+        /// <exception cref="NotSupportedException">Not supported</exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<SwitchUserCompletionEventArgs> SwitchUserCompletion
+        {
+            add
+            {
+                lock (_switchUserCompletionLock)
+                {
+                    if (_switchUserCompletionHandler == null)
+                        RegisterCallbackForEvent(SessionEventType.SwitchUserCompletion, _switchUserCompletionCB, OnSwitchUserCompletion);
+                    _switchUserCompletionHandler += value;
+                }
+            }
+            remove
+            {
+                lock (_switchUserCompletionLock)
+                {
+                    _switchUserCompletionHandler -= value;
+                    if (_switchUserCompletionHandler == null)
+                        UnregisterCallbackForEvent(SessionEventType.SwitchUserCompletion, _switchUserCompletionCB);
+                }
+            }
+        }
+
+        private void CheckError(SessionError ret, string msg)
+        {
+            if (ret == SessionError.None)
+                return;
+
+            Log.Error(SessionErrorFactory.LogTag, msg);
+            SessionErrorFactory.ThrowException(ret);
+        }
+
+        private void IntPtrToStringArray(IntPtr unmanagedArray, int size, out string[] managedArray)
+        {
+            managedArray = new string[size];
+
+            byte[] byteArray = new byte[MaxUserLength * size];
+            byte[] tmpArray = new byte[MaxUserLength];
+
+            Marshal.Copy(unmanagedArray, byteArray, 0, MaxUserLength * size);
+
+            for (int i = 0; i < size; i++)
+            {
+                Array.Copy(byteArray, MaxUserLength * i, tmpArray, 0, MaxUserLength);
+                managedArray[i] = Encoding.UTF8.GetString(tmpArray);
+            }
+        }
+
+        private void RegisterCallbackForEvent(SessionEventType eventType, Interop.Session.SubsessionEventCallback eventCallback,
+            EventDelegate delegateToSet)
+        {
+            eventCallback = new Interop.Session.SubsessionEventCallback(delegateToSet);
+            SessionError ret = Interop.Session.SubesssionRegisterEventCallback(SessionUID, eventType,
+                eventCallback, IntPtr.Zero);
+            CheckError(ret, $"Interop failed to register a callback for an event of type {eventType}");
+        }
+
+        private void UnregisterCallbackForEvent(SessionEventType eventType, Interop.Session.SubsessionEventCallback eventCallback)
+        {
+            SessionError ret = Interop.Session.SubesssionUnregisterEventCallback(SessionUID, eventType);
+            CheckError(ret, $"Interop failed to unregister a callback for an event of type {eventType}");
+            eventCallback = null;
+        }
+    }
+}
diff --git a/src/Tizen.System.Session/Session/SessionEnums.cs b/src/Tizen.System.Session/Session/SessionEnums.cs
new file mode 100644 (file)
index 0000000..e5866c8
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System.ComponentModel;
+
+namespace Tizen.System
+{
+    internal enum SessionEventType : int
+    {
+        AddUserWait = 1 << 0,
+        RemoveUserWait = 1 << 1,
+        SwitchUserWait = 1 << 2,
+        SwitchUserCompletion = 1 << 3,
+    }
+}
diff --git a/src/Tizen.System.Session/Session/SessionErrorFactory.cs b/src/Tizen.System.Session/Session/SessionErrorFactory.cs
new file mode 100644 (file)
index 0000000..70f407b
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+* Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the License);
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an AS IS BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.IO;
+using Tizen.Internals.Errors;
+
+namespace Tizen.System
+{
+    internal enum SessionError
+    {
+        None = ErrorCode.None,
+        InvalidParameter = ErrorCode.InvalidParameter,
+        Io = ErrorCode.IoError,
+        OutOfMemory = ErrorCode.OutOfMemory,
+        AlreadyExists = ErrorCode.FileExists,
+        NotAvailible = ErrorCode.NoSuchDevice,
+        ResourceBusy = ErrorCode.ResourceBusy,
+        PermissionDenied = ErrorCode.PermissionDenied,
+        NotSupported = ErrorCode.NotSupported,
+    }
+
+    internal static class SessionErrorFactory
+    {
+        internal const string LogTag = "Tizen.System.Session";
+
+        internal static void ThrowException(SessionError err)
+        {
+            SessionError error = (SessionError)err;
+            if (error == SessionError.InvalidParameter)
+            {
+                throw new ArgumentException("Invalid parameter");
+            }
+            else if (error == SessionError.Io)
+            {
+                throw new IOException("I/O error");
+            }
+            else if (error == SessionError.OutOfMemory)
+            {
+                throw new OutOfMemoryException("Out of memory");
+            }
+            else if (error == SessionError.AlreadyExists)
+            {
+                throw new InvalidOperationException("Already exists");
+            }
+            else if (error == SessionError.NotAvailible)
+            {
+                throw new InvalidOperationException("Not availible");
+            }
+            else if (error == SessionError.ResourceBusy)
+            {
+                throw new InvalidOperationException("Resource busy");
+            }
+            else if (error == SessionError.PermissionDenied)
+            {
+                throw new UnauthorizedAccessException("Permission denied");
+            }
+            else if (error == SessionError.NotSupported)
+            {
+                throw new NotSupportedException("Not supported");
+            }
+        }
+    }
+}
diff --git a/src/Tizen.System.Session/Session/SessionEventArgs.cs b/src/Tizen.System.Session/Session/SessionEventArgs.cs
new file mode 100644 (file)
index 0000000..85d7799
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+* Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the License);
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an AS IS BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.ComponentModel;
+
+namespace Tizen.System
+{
+    /// <summary>
+    /// A generic subession event argument type.
+    /// </summary>
+    /// <remarks>
+    /// Use this as an argument type for generic subession event handlers.
+    /// You can check the event type that was invoked by checking a type of event arguments
+    /// during runtime - they all derive from this base class.
+    /// </remarks>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public abstract class SubsessionEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Session UID of the session invoking the event
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public int SessionUID { get; internal set; }
+
+        internal SubsessionEventInfoNative SessionInfo { get; set; }
+
+        internal SubsessionEventArgs(SubsessionEventInfoNative infoNative)
+        {
+            SessionUID = infoNative.sessionUID;
+            SessionInfo = infoNative;
+        }
+    }
+
+    /// <summary>
+    /// An event arguemnt type for AddUserWait event type
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class AddUserEventArgs : SubsessionEventArgs
+    {
+        /// <summary>
+        /// Added subsession user ID
+        /// </summary> 
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string UserName { get; internal set; }
+
+        internal AddUserEventArgs(SubsessionEventInfoNative infoNative)
+            : base(infoNative)
+        {
+            UserName = infoNative.addUser.userName;
+        }
+    }
+
+    /// <summary>
+    /// An event arguemnt type for RemoveUserWait event type
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class RemoveUserEventArgs : SubsessionEventArgs
+    {
+        /// <summary>
+        /// Removed subsession user ID
+        /// </summary> 
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string UserName { get; internal set; }
+
+        internal RemoveUserEventArgs(SubsessionEventInfoNative infoNative)
+            : base(infoNative)
+        {
+            UserName = infoNative.removeUser.userName;
+        }
+    }
+
+    /// <summary>
+    /// A generic base class for Switch event types
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public abstract class SwitchUserEventArgs : SubsessionEventArgs
+    {
+        /// <summary>
+        /// ID of this switch operation
+        /// </summary> 
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public long SwitchID { get; internal set; }
+
+        /// <summary>
+        /// Active subsession user ID before this switch operation
+        /// </summary> 
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string UserNamePrev { get; internal set; }
+
+        /// <summary>
+        /// Active subsession ID after this switch operation
+        /// </summary> 
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public string UserNameNext { get; internal set; }
+
+        internal SwitchUserEventArgs(SubsessionEventInfoNative infoNative)
+            : base(infoNative)
+        {
+            SwitchID = infoNative.switchUser.switchID;
+            UserNamePrev = infoNative.switchUser.userNamePrev;
+            UserNameNext = infoNative.switchUser.userNameNext;
+        }
+    }
+
+    /// <summary>
+    /// An event arguemnt type for SwitchUserWait event type
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class SwitchUserWaitEventArgs : SwitchUserEventArgs
+    {
+        internal SwitchUserWaitEventArgs(SubsessionEventInfoNative infoNative) : base(infoNative) { }
+    }
+
+    /// <summary>
+    /// An event arguemnt type for SwitchUserCompletion event type
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class SwitchUserCompletionEventArgs : SwitchUserEventArgs
+    {
+        internal SwitchUserCompletionEventArgs(SubsessionEventInfoNative infoNative) : base(infoNative) { }
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.System.Session/Session/SessionStructs.cs b/src/Tizen.System.Session/Session/SessionStructs.cs
new file mode 100644 (file)
index 0000000..b0db254
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Text;
+using Tizen.Internals;
+
+namespace Tizen.System
+{
+    [NativeStruct("subsession_event_info", Include = "sessiond.h", PkgConfig = "libsessiond")]
+    [StructLayout(LayoutKind.Explicit)]
+    internal struct SubsessionEventInfoNative
+    {
+        [FieldOffset(0)]
+        public SessionEventType eventType;
+        [FieldOffset(4)]
+        public int sessionUID;
+        [FieldOffset(8)]
+        public AddUser addUser;
+        [FieldOffset(8)]
+        public RemoveUser removeUser;
+        [FieldOffset(8)]
+        public SwitchUser switchUser;
+    }
+
+    /*
+
+       Regarding AddUser, RemoveUser and SwitchUser structs:
+
+       Marshaling complex C/C++ structs containing C/C++ unions as their members is tricky.
+       Firstly, to handle C/C++ unions in C# one needs to provide an explicit memory layout.
+       It's vital to provide a correct alignment. Since our fields in the native C struct being
+       reflected here are already all divisible by 4, there's no need for fillers.
+       Secondly, when the C# marshaller encounters an array, it creates a managed counterpart
+       on the heap, but what we'll get on the stack is actually the reference to the array on the heap.
+       Any other structure containing basic types would be overlapping and thus invalidating that
+       reference (remember we're dealing with a union where all the elements (structs in our case)
+       start form the same address) and leading to a nasty run-time error:
+       "it contains an object field at offset X that is incorrectly aligned
+       or overlapped by a non-object field".
+       The only solution that works 100% of the times is unrolling the arrays down to
+       single bytes and turn off the C# marshaller.
+       Unfortunately the code is not pretty, but they say maturity is consent to an unfulfilled life.
+
+       */
+
+    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
+    internal struct AddUser
+    {
+        public byte userName_00;
+        public byte userName_01;
+        public byte userName_02;
+        public byte userName_03;
+        public byte userName_04;
+        public byte userName_05;
+        public byte userName_06;
+        public byte userName_07;
+        public byte userName_08;
+        public byte userName_09;
+        public byte userName_10;
+        public byte userName_11;
+        public byte userName_12;
+        public byte userName_13;
+        public byte userName_14;
+        public byte userName_15;
+        public byte userName_16;
+        public byte userName_17;
+        public byte userName_18;
+        public byte userName_19;
+
+        public string userName
+        {
+            get
+            {
+                return UnrolledBytesToStringConverter.Convert20(
+                    userName_00, userName_01, userName_02, userName_03,
+                    userName_04, userName_05, userName_06, userName_07,
+                    userName_08, userName_09, userName_10, userName_11,
+                    userName_12, userName_13, userName_14, userName_15,
+                    userName_16, userName_17, userName_18, userName_19
+                );
+            }
+        }
+    }
+
+    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
+    internal struct RemoveUser
+    {
+        public byte userName_00;
+        public byte userName_01;
+        public byte userName_02;
+        public byte userName_03;
+        public byte userName_04;
+        public byte userName_05;
+        public byte userName_06;
+        public byte userName_07;
+        public byte userName_08;
+        public byte userName_09;
+        public byte userName_10;
+        public byte userName_11;
+        public byte userName_12;
+        public byte userName_13;
+        public byte userName_14;
+        public byte userName_15;
+        public byte userName_16;
+        public byte userName_17;
+        public byte userName_18;
+        public byte userName_19;
+        public string userName
+        {
+            get
+            {
+                return UnrolledBytesToStringConverter.Convert20(
+                    userName_00, userName_01, userName_02, userName_03,
+                    userName_04, userName_05, userName_06, userName_07,
+                    userName_08, userName_09, userName_10, userName_11,
+                    userName_12, userName_13, userName_14, userName_15,
+                    userName_16, userName_17, userName_18, userName_19
+                );
+            }
+        }
+    }
+    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
+    internal struct SwitchUser
+    {
+        public Int64 switchID;
+        public byte userNamePrev_00;
+        public byte userNamePrev_01;
+        public byte userNamePrev_02;
+        public byte userNamePrev_03;
+        public byte userNamePrev_04;
+        public byte userNamePrev_05;
+        public byte userNamePrev_06;
+        public byte userNamePrev_07;
+        public byte userNamePrev_08;
+        public byte userNamePrev_09;
+        public byte userNamePrev_10;
+        public byte userNamePrev_11;
+        public byte userNamePrev_12;
+        public byte userNamePrev_13;
+        public byte userNamePrev_14;
+        public byte userNamePrev_15;
+        public byte userNamePrev_16;
+        public byte userNamePrev_17;
+        public byte userNamePrev_18;
+        public byte userNamePrev_19;
+
+        public string userNamePrev
+        {
+            get
+            {
+                return UnrolledBytesToStringConverter.Convert20(
+                    userNamePrev_00, userNamePrev_01, userNamePrev_02, userNamePrev_03,
+                    userNamePrev_04, userNamePrev_05, userNamePrev_06, userNamePrev_07,
+                    userNamePrev_08, userNamePrev_09, userNamePrev_10, userNamePrev_11,
+                    userNamePrev_12, userNamePrev_13, userNamePrev_14, userNamePrev_15,
+                    userNamePrev_16, userNamePrev_17, userNamePrev_18, userNamePrev_19
+                );
+            }
+        }
+
+        public byte userNameNext_00;
+        public byte userNameNext_01;
+        public byte userNameNext_02;
+        public byte userNameNext_03;
+        public byte userNameNext_04;
+        public byte userNameNext_05;
+        public byte userNameNext_06;
+        public byte userNameNext_07;
+        public byte userNameNext_08;
+        public byte userNameNext_09;
+        public byte userNameNext_10;
+        public byte userNameNext_11;
+        public byte userNameNext_12;
+        public byte userNameNext_13;
+        public byte userNameNext_14;
+        public byte userNameNext_15;
+        public byte userNameNext_16;
+        public byte userNameNext_17;
+        public byte userNameNext_18;
+        public byte userNameNext_19;
+
+        public string userNameNext
+        {
+            get
+            {
+                return UnrolledBytesToStringConverter.Convert20(
+                    userNameNext_00, userNameNext_01, userNameNext_02, userNameNext_03,
+                    userNameNext_04, userNameNext_05, userNameNext_06, userNameNext_07,
+                    userNameNext_08, userNameNext_09, userNameNext_10, userNameNext_11,
+                    userNameNext_12, userNameNext_13, userNameNext_14, userNameNext_15,
+                    userNameNext_16, userNameNext_17, userNameNext_18, userNameNext_19
+                );
+            }
+        }
+    }
+
+    internal static class UnrolledBytesToStringConverter
+    {
+        public static string Convert20(
+            byte val_00, byte val_01, byte val_02, byte val_03,
+            byte val_04, byte val_05, byte val_06, byte val_07,
+            byte val_08, byte val_09, byte val_10, byte val_11,
+            byte val_12, byte val_13, byte val_14, byte val_15,
+            byte val_16, byte val_17, byte val_18, byte val_19)
+        {
+            byte[] toConvert = new byte[]
+            {
+                    val_00, val_01, val_02, val_03,
+                    val_04, val_05, val_06, val_07,
+                    val_08, val_09, val_10, val_11,
+                    val_12, val_13, val_14, val_15,
+                    val_16, val_17, val_18, val_19
+            };
+
+            return Encoding.UTF8.GetString(toConvert);
+        }
+    }
+}
diff --git a/src/Tizen.System.Session/Tizen.System.Session.csproj b/src/Tizen.System.Session/Tizen.System.Session.csproj
new file mode 100644 (file)
index 0000000..1407501
--- /dev/null
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Tizen\Tizen.csproj" />
+    <ProjectReference Include="..\Tizen.Log\Tizen.Log.csproj" />
+  </ItemGroup>
+
+</Project>