2 * Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using System.Collections.Concurrent;
19 using System.Collections.Generic;
20 using System.ComponentModel;
22 using System.Runtime.InteropServices;
24 using System.Threading.Tasks;
26 namespace Tizen.System
29 /// Provides methods to manage subsession users. Allows to register for events triggered by operations on subsession users.
31 [EditorBrowsable(EditorBrowsableState.Never)]
32 public sealed class Session
35 /// UID of current system user.
37 [EditorBrowsable(EditorBrowsableState.Never)]
38 public static readonly int CurrentUID;
41 /// Maximum length of any given user ID.
43 [EditorBrowsable(EditorBrowsableState.Never)]
44 public const int MaxUserLength = 20;
47 /// Special subsession ID, which is always present and does not represent any user.
49 [EditorBrowsable(EditorBrowsableState.Never)]
50 public const string EmptyUser = "";
52 private static ConcurrentDictionary<int, Session> s_sessionInstances = new ConcurrentDictionary<int, Session>();
54 private readonly object _replyLock = new object();
56 private IDictionary<int, Interop.Session.SubsessionReplyCallback> _replyMap = new ConcurrentDictionary<int, Interop.Session.SubsessionReplyCallback>();
58 private int _replyID = 0;
60 private delegate void EventDelegate(IntPtr infoNative, IntPtr data);
64 CurrentUID = Interop.Session.GetUID();
67 private Session(int sessionUID)
69 SessionUID = sessionUID;
73 /// Gets a Session object instance for a given sessionUID.
75 /// <param name="sessionUID">Session UID of a requested Session object instance</param>
76 /// <returns>Returns requested Session object</returns>
78 /// To ensure thread safety, expilicit creation of Session object is not allowed.
80 [EditorBrowsable(EditorBrowsableState.Never)]
81 public static Session GetInstance(int sessionUID)
83 if (!s_sessionInstances.ContainsKey(sessionUID))
84 s_sessionInstances.TryAdd(sessionUID, new Session(sessionUID));
85 return s_sessionInstances[sessionUID];
89 /// Gets session UID of this session object.
91 [EditorBrowsable(EditorBrowsableState.Never)]
92 public int SessionUID { get; private set; }
95 /// Gets a list of all availible subsession user IDs for this session.
98 /// The list of users depends on whether the session UID for this session object exists or not. If it
99 /// doesn't, the user list is empty (in particular this is not an error).
100 /// However if the session UID exists, the user list will contain the subsession
101 /// IDs (if they exist), but also the default value, which is "" (empty string, see EmptyUser field).
102 /// 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
105 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
106 /// <exception cref="OutOfMemoryException">Out of memory</exception>
107 /// <exception cref="IOException">Internal error</exception>
108 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
109 /// <exception cref="NotSupportedException">Not supported</exception>
110 [EditorBrowsable(EditorBrowsableState.Never)]
111 public IReadOnlyList<string> GetUsers()
117 SessionError ret = Interop.Session.SubsessionGetUserList(SessionUID, out ptr, out count);
118 CheckError(ret, "Interop failed to get user list");
121 IntPtrToStringArray(ptr, count, out users);
122 Interop.Session.SubsessionFreeUserList(ptr);
124 return new List<string>(users);
128 /// Gets a currently active subession user ID for this session.
131 /// When no subsession is enabled, "" (empty string, see EmptyUser field) is returned.
132 /// This doesn't mean that "" is a subsession ID in the same way as others; it is just a marker meaning
133 /// that no subsession is enabled.
135 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
136 /// <exception cref="OutOfMemoryException">Out of memory</exception>
137 /// <exception cref="IOException">Internal error</exception>
138 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
139 /// <exception cref="NotSupportedException">Not supported</exception>
140 [EditorBrowsable(EditorBrowsableState.Never)]
141 public string GetCurrentUser()
143 StringBuilder user = new StringBuilder(MaxUserLength);
144 SessionError ret = Interop.Session.SubsessionGetCurrentUser(SessionUID, user);
145 CheckError(ret, "Interop failed to get current subsession user");
147 return user.ToString();
151 /// Request new subsession to be created.
153 /// <param name="userName">Subesssion user ID to be created</param>
155 /// Subsession ID must not start with a dot or have slashes.
157 /// <exception cref="ArgumentException">Session UID of this object is invalid, or user ID is not a valid subession ID</exception>
158 /// <exception cref="InvalidOperationException"> Provided subsession user ID already exists</exception>
159 /// <exception cref="OutOfMemoryException">Out of memory</exception>
160 [EditorBrowsable(EditorBrowsableState.Never)]
161 public Task SubsessionAddUserAsync(string userName)
163 var task = new TaskCompletionSource<bool>();
171 _replyMap[taskID] = (int result, IntPtr data) =>
175 CheckError((SessionError)result, "Interop failed to complete adding a new subsession user");
176 task.SetResult(true);
178 catch (Exception exception)
180 task.SetException(exception);
184 _replyMap.Remove((int)data);
188 SessionError ret = Interop.Session.SubsessionAddUser(SessionUID, userName, _replyMap[taskID], (IntPtr)taskID);
189 CheckError(ret, "Interop failed to register a reply for adding a user");
194 /// Request an existing subsession to be removed.
196 /// <param name="userName">Existing subesssion user ID to be removed</param>
198 /// Subsession ID must not start with a dot or have slashes.
199 /// Only inactive session ID can be removed. In order remove currently used session ID first switch to special
200 /// session ID "" (empty string, see EmptyUser), and only after switch completes, remove previously active session ID.
202 /// <exception cref="ArgumentException">Session UID of this object is invalid, or user ID is not a valid subession ID</exception>
203 /// <exception cref="InvalidOperationException">Provided subsession user ID does not exist</exception>
204 /// <exception cref="OutOfMemoryException">Out of memory</exception>
205 [EditorBrowsable(EditorBrowsableState.Never)]
206 public Task SubsessionRemoveUserAsync(string userName)
208 var task = new TaskCompletionSource<bool>();
216 _replyMap[taskID] = (int result, IntPtr data) =>
220 CheckError((SessionError)result, "Interop failed to remove a subsession user");
221 task.SetResult(true);
223 catch (Exception exception)
225 task.SetException(exception);
229 _replyMap.Remove((int)data);
233 SessionError ret = Interop.Session.SubsessionRemoveUser(SessionUID, userName, _replyMap[taskID], (IntPtr)taskID);
234 CheckError(ret, "Interop failed to register a reply for removing a user");
239 /// Request a subession to become currently active.
241 /// <param name="userName">Existing subesssion user ID to be set as active</param>
243 /// Subsession ID must not start with a dot or have slashes.
244 /// Special subsession ID "" (empty string, see EmptyUser) can be switched to, when it's required to deactivate
245 /// current subsession (this step is needed when current session is to be removed).
247 /// <exception cref="ArgumentException">Session UID of this object is invalid, or user ID is not a valid subession ID</exception>
248 /// <exception cref="InvalidOperationException">Provided subsession user ID to switch to does not exist</exception>
249 /// <exception cref="OutOfMemoryException">Out of memory</exception>
250 [EditorBrowsable(EditorBrowsableState.Never)]
251 public Task SubsessionSwitchUserAsync(string userName)
253 var task = new TaskCompletionSource<bool>();
261 _replyMap[taskID] = (int result, IntPtr data) =>
265 CheckError((SessionError)result, "Interop failed to switch to a different subsession user");
266 task.SetResult(true);
268 catch (Exception exception)
270 task.SetException(exception);
274 _replyMap.Remove((int)data);
278 SessionError ret = Interop.Session.SubsessionSwitchUser(SessionUID, userName, _replyMap[taskID], (IntPtr)taskID);
279 CheckError(ret, "Interop failed to register a reply for switching a user");
284 /// Mark event as completed.
286 /// <param name="subsessionEventArgs">Event argument of the event (obtained from said event)</param>
288 /// This method is assumed to be called from an event handler. You can only mark an event as completed
289 /// if you registered for in in the same process.
291 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
292 /// <exception cref="IOException">Internal error</exception>
293 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
294 /// <exception cref="NotSupportedException">Not supported</exception>
295 [EditorBrowsable(EditorBrowsableState.Never)]
296 public void SubsessionEventMarkAsDone(SubsessionEventArgs subsessionEventArgs)
298 SessionError ret = Interop.Session.SubsessionEventWaitDone(subsessionEventArgs.SessionInfo);
299 CheckError(ret, $"Interop failed to mark this client's event (of type {subsessionEventArgs.SessionInfo.eventType}) as finished");
302 private void OnAddUserWait(IntPtr infoNative, IntPtr data)
304 _addUserWaitHandler?.Invoke(this, new AddUserEventArgs(infoNative));
307 private Interop.Session.SubsessionEventCallback _addUserWaitCB = null;
309 private event EventHandler<AddUserEventArgs> _addUserWaitHandler = null;
311 private readonly object _addUserWaitLock = new object();
314 /// Event to be invoked when a new subession user is successfully added to this session.
316 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
317 /// <exception cref="OutOfMemoryException">Out of memory</exception>
318 /// <exception cref="IOException">Internal error</exception>
319 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
320 /// <exception cref="NotSupportedException">Not supported</exception>
321 [EditorBrowsable(EditorBrowsableState.Never)]
322 public event EventHandler<AddUserEventArgs> AddUserWait
326 lock (_addUserWaitLock)
328 if (_addUserWaitHandler == null)
329 RegisterCallbackForEvent(SessionEventType.AddUserWait, ref _addUserWaitCB, OnAddUserWait);
330 _addUserWaitHandler += value;
335 lock (_addUserWaitLock)
337 _addUserWaitHandler -= value;
338 if (_addUserWaitHandler == null)
339 UnregisterCallbackForEvent(SessionEventType.AddUserWait, ref _addUserWaitCB);
344 private void OnRemoveUserWait(IntPtr infoNative, IntPtr data)
346 _removeUserWaitHandler?.Invoke(this, new RemoveUserEventArgs(infoNative));
349 private Interop.Session.SubsessionEventCallback _removeUserWaitCB = null;
351 private event EventHandler<RemoveUserEventArgs> _removeUserWaitHandler = null;
353 private readonly object _removeUserWaitLock = new object();
356 /// Event to be invoked when a subession user is successfully removed from this session.
358 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
359 /// <exception cref="OutOfMemoryException">Out of memory</exception>
360 /// <exception cref="IOException">Internal error</exception>
361 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
362 /// <exception cref="NotSupportedException">Not supported</exception>
363 [EditorBrowsable(EditorBrowsableState.Never)]
364 public event EventHandler<RemoveUserEventArgs> RemoveUserWait
368 lock (_removeUserWaitLock)
370 if (_removeUserWaitHandler == null)
371 RegisterCallbackForEvent(SessionEventType.RemoveUserWait, ref _removeUserWaitCB, OnRemoveUserWait);
372 _removeUserWaitHandler += value;
377 lock (_removeUserWaitLock)
379 _removeUserWaitHandler -= value;
380 if (_removeUserWaitHandler == null)
381 UnregisterCallbackForEvent(SessionEventType.RemoveUserWait, ref _removeUserWaitCB);
386 private void OnSwitchUserWait(IntPtr infoNative, IntPtr data)
388 _switchUserWaitHandler?.Invoke(this, new SwitchUserWaitEventArgs(infoNative));
391 private Interop.Session.SubsessionEventCallback _switchUserWaitCB = null;
393 private event EventHandler<SwitchUserWaitEventArgs> _switchUserWaitHandler = null;
395 private readonly object _switchUserWaitLock = new object();
398 /// Event to be invoked when an existing subession user has begun switching to an active state.
400 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
401 /// <exception cref="OutOfMemoryException">Out of memory</exception>
402 /// <exception cref="IOException">Internal error</exception>
403 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
404 /// <exception cref="NotSupportedException">Not supported</exception>
405 [EditorBrowsable(EditorBrowsableState.Never)]
406 public event EventHandler<SwitchUserWaitEventArgs> SwitchUserWait
410 lock (_switchUserWaitLock)
412 if (_switchUserWaitHandler == null)
413 RegisterCallbackForEvent(SessionEventType.SwitchUserWait, ref _switchUserWaitCB, OnSwitchUserWait);
414 _switchUserWaitHandler += value;
419 lock (_switchUserWaitLock)
421 _switchUserWaitHandler -= value;
422 if (_switchUserWaitHandler == null)
423 UnregisterCallbackForEvent(SessionEventType.SwitchUserWait, ref _switchUserWaitCB);
428 private void OnSwitchUserCompletion(IntPtr infoNative, IntPtr data)
430 _switchUserCompletionHandler?.Invoke(this, new SwitchUserCompletionEventArgs(infoNative));
433 private Interop.Session.SubsessionEventCallback _switchUserCompletionCB = null;
435 private event EventHandler<SwitchUserCompletionEventArgs> _switchUserCompletionHandler = null;
437 private readonly object _switchUserCompletionLock = new object();
440 /// Event to be invoked when an existing subession user is successfully switched to an acvite state.
442 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
443 /// <exception cref="OutOfMemoryException">Out of memory</exception>
444 /// <exception cref="IOException">Internal error</exception>
445 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
446 /// <exception cref="NotSupportedException">Not supported</exception>
447 [EditorBrowsable(EditorBrowsableState.Never)]
448 public event EventHandler<SwitchUserCompletionEventArgs> SwitchUserCompleted
452 lock (_switchUserCompletionLock)
454 if (_switchUserCompletionHandler == null)
455 RegisterCallbackForEvent(SessionEventType.SwitchUserCompletion, ref _switchUserCompletionCB, OnSwitchUserCompletion);
456 _switchUserCompletionHandler += value;
461 lock (_switchUserCompletionLock)
463 _switchUserCompletionHandler -= value;
464 if (_switchUserCompletionHandler == null)
465 UnregisterCallbackForEvent(SessionEventType.SwitchUserCompletion, ref _switchUserCompletionCB);
470 private void CheckError(SessionError ret, string msg)
472 if (ret == SessionError.None)
475 Log.Error(SessionErrorFactory.LogTag, msg);
476 Exception ex = SessionErrorFactory.CreateException(ret);
479 Log.Error(SessionErrorFactory.LogTag,
480 "Unexpected exception type for SessionError: " + Enum.GetName(typeof(SessionError), ret));
481 throw new InvalidOperationException("Unrecognized error");
486 static void IntPtrToStringArray(IntPtr unmanagedArray, int size, out string[] managedArray)
488 managedArray = new string[size];
489 var curr = unmanagedArray;
491 for (int iterator = 0; iterator < size; iterator++)
493 managedArray[iterator] = Marshal.PtrToStringAnsi(curr, 20);
494 curr = IntPtr.Add(curr, 20);
498 private void RegisterCallbackForEvent(SessionEventType eventType, ref Interop.Session.SubsessionEventCallback eventCallback,
499 EventDelegate delegateToSet)
501 eventCallback = new Interop.Session.SubsessionEventCallback(delegateToSet);
502 SessionError ret = Interop.Session.SubesssionRegisterEventCallback(SessionUID, eventType,
503 eventCallback, IntPtr.Zero);
504 CheckError(ret, $"Interop failed to register a callback for an event of type {eventType}");
507 private void UnregisterCallbackForEvent(SessionEventType eventType, ref Interop.Session.SubsessionEventCallback eventCallback)
509 SessionError ret = Interop.Session.SubesssionUnregisterEventCallback(SessionUID, eventType);
510 CheckError(ret, $"Interop failed to unregister a callback for an event of type {eventType}");
511 eventCallback = null;