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(SubsessionEventInfoNative 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) =>
173 _replyMap.Remove((int)data);
176 CheckError((SessionError)result, "Interop failed to complete adding a new subsession user");
178 catch (Exception exception)
180 task.SetException(exception);
184 task.SetResult(true);
187 SessionError ret = Interop.Session.SubsessionAddUser(SessionUID, userName, _replyMap[taskID], (IntPtr)taskID);
188 CheckError(ret, "Interop failed to register a reply for adding a user");
193 /// Request an existing subsession to be removed.
195 /// <param name="userName">Existing subesssion user ID to be removed</param>
197 /// Subsession ID must not start with a dot or have slashes.
198 /// Only inactive session ID can be removed. In order remove currently used session ID first switch to special
199 /// session ID "" (empty string, see EmptyUser), and only after switch completes, remove previously active session ID.
201 /// <exception cref="ArgumentException">Session UID of this object is invalid, or user ID is not a valid subession ID</exception>
202 /// <exception cref="InvalidOperationException">Provided subsession user ID does not exist</exception>
203 /// <exception cref="OutOfMemoryException">Out of memory</exception>
204 [EditorBrowsable(EditorBrowsableState.Never)]
205 public Task SubsessionRemoveUserAsync(string userName)
207 var task = new TaskCompletionSource<bool>();
215 _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);
230 _replyMap.Remove((int)data);
234 SessionError ret = Interop.Session.SubsessionRemoveUser(SessionUID, userName, _replyMap[taskID], (IntPtr)taskID);
235 CheckError(ret, "Interop failed to register a reply for removing a user");
240 /// Request a subession to become currently active.
242 /// <param name="userName">Existing subesssion user ID to be set as active</param>
244 /// Subsession ID must not start with a dot or have slashes.
245 /// Special subsession ID "" (empty string, see EmptyUser) can be switched to, when it's required to deactivate
246 /// current subsession (this step is needed when current session is to be removed).
248 /// <exception cref="ArgumentException">Session UID of this object is invalid, or user ID is not a valid subession ID</exception>
249 /// <exception cref="InvalidOperationException">Provided subsession user ID to switch to does not exist</exception>
250 /// <exception cref="OutOfMemoryException">Out of memory</exception>
251 [EditorBrowsable(EditorBrowsableState.Never)]
252 public Task SubsessionSwitchUserAsync(string userName)
254 var task = new TaskCompletionSource<bool>();
262 _replyMap[taskID] = (int result, IntPtr data) =>
266 CheckError((SessionError)result, "Interop failed to switch to a different subsession user");
267 task.SetResult(true);
269 catch (Exception exception)
271 task.SetException(exception);
276 _replyMap.Remove((int)data);
280 SessionError ret = Interop.Session.SubsessionSwitchUser(SessionUID, userName, _replyMap[taskID], (IntPtr)taskID);
281 CheckError(ret, "Interop failed to register a reply for switching a user");
286 /// Mark event as completed.
288 /// <param name="subsessionEventArgs">Event argument of the event (obtained from said event)</param>
290 /// This method is assumed to be called from an event handler. You can only mark an event as completed
291 /// if you registered for in in the same process.
293 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
294 /// <exception cref="IOException">Internal error</exception>
295 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
296 /// <exception cref="NotSupportedException">Not supported</exception>
297 [EditorBrowsable(EditorBrowsableState.Never)]
298 public void SubsessionEventMarkAsDone(SubsessionEventArgs subsessionEventArgs)
300 SessionError ret = Interop.Session.SubsessionEventWaitDone(subsessionEventArgs.SessionInfo);
301 CheckError(ret, $"Interop failed to mark this client's event (of type {subsessionEventArgs.SessionInfo.eventType}) as finished");
304 private void OnAddUserWait(SubsessionEventInfoNative infoNative, IntPtr data)
306 _addUserWaitHandler?.Invoke(this, new AddUserEventArgs(infoNative));
309 private Interop.Session.SubsessionEventCallback _addUserWaitCB = null;
311 private event EventHandler<AddUserEventArgs> _addUserWaitHandler = null;
313 private readonly object _addUserWaitLock = new object();
316 /// Event to be invoked when a new subession user is successfully added to this session.
318 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
319 /// <exception cref="OutOfMemoryException">Out of memory</exception>
320 /// <exception cref="IOException">Internal error</exception>
321 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
322 /// <exception cref="NotSupportedException">Not supported</exception>
323 [EditorBrowsable(EditorBrowsableState.Never)]
324 public event EventHandler<AddUserEventArgs> AddUserWait
328 lock (_addUserWaitLock)
330 if (_addUserWaitHandler == null)
331 RegisterCallbackForEvent(SessionEventType.AddUserWait, _addUserWaitCB, OnAddUserWait);
332 _addUserWaitHandler += value;
337 lock (_addUserWaitLock)
339 _addUserWaitHandler -= value;
340 if (_addUserWaitHandler == null)
341 UnregisterCallbackForEvent(SessionEventType.AddUserWait, _addUserWaitCB);
346 private void OnRemoveUserWait(SubsessionEventInfoNative infoNative, IntPtr data)
348 _removeUserWaitHandler?.Invoke(this, new RemoveUserEventArgs(infoNative));
351 private Interop.Session.SubsessionEventCallback _removeUserWaitCB = null;
353 private event EventHandler<RemoveUserEventArgs> _removeUserWaitHandler = null;
355 private readonly object _removeUserWaitLock = new object();
358 /// Event to be invoked when a subession user is successfully removed from this session.
360 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
361 /// <exception cref="OutOfMemoryException">Out of memory</exception>
362 /// <exception cref="IOException">Internal error</exception>
363 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
364 /// <exception cref="NotSupportedException">Not supported</exception>
365 [EditorBrowsable(EditorBrowsableState.Never)]
366 public event EventHandler<RemoveUserEventArgs> RemoveUserWait
370 lock (_removeUserWaitLock)
372 if (_removeUserWaitHandler == null)
373 RegisterCallbackForEvent(SessionEventType.RemoveUserWait, _removeUserWaitCB, OnRemoveUserWait);
374 _removeUserWaitHandler += value;
379 lock (_removeUserWaitLock)
381 _removeUserWaitHandler -= value;
382 if (_removeUserWaitHandler == null)
383 UnregisterCallbackForEvent(SessionEventType.RemoveUserWait, _removeUserWaitCB);
388 private void OnSwitchUserWait(SubsessionEventInfoNative infoNative, IntPtr data)
390 _switchUserWaitHandler?.Invoke(this, new SwitchUserWaitEventArgs(infoNative));
393 private Interop.Session.SubsessionEventCallback _switchUserWaitCB = null;
395 private event EventHandler<SwitchUserWaitEventArgs> _switchUserWaitHandler = null;
397 private readonly object _switchUserWaitLock = new object();
400 /// Event to be invoked when an existing subession user has begun switching to an active state.
402 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
403 /// <exception cref="OutOfMemoryException">Out of memory</exception>
404 /// <exception cref="IOException">Internal error</exception>
405 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
406 /// <exception cref="NotSupportedException">Not supported</exception>
407 [EditorBrowsable(EditorBrowsableState.Never)]
408 public event EventHandler<SwitchUserWaitEventArgs> SwitchUserWait
412 lock (_switchUserWaitLock)
414 if (_switchUserWaitHandler == null)
415 RegisterCallbackForEvent(SessionEventType.SwitchUserWait, _switchUserWaitCB, OnSwitchUserWait);
416 _switchUserWaitHandler += value;
421 lock (_switchUserWaitLock)
423 _switchUserWaitHandler -= value;
424 if (_switchUserWaitHandler == null)
425 UnregisterCallbackForEvent(SessionEventType.SwitchUserWait, _switchUserWaitCB);
430 private void OnSwitchUserCompletion(SubsessionEventInfoNative infoNative, IntPtr data)
432 _switchUserCompletionHandler?.Invoke(this, new SwitchUserCompletionEventArgs(infoNative));
435 private Interop.Session.SubsessionEventCallback _switchUserCompletionCB = null;
437 private event EventHandler<SwitchUserCompletionEventArgs> _switchUserCompletionHandler = null;
439 private readonly object _switchUserCompletionLock = new object();
442 /// Event to be invoked when an existing subession user is successfully switched to an acvite state.
444 /// <exception cref="ArgumentException">Session UID of this object is invalid</exception>
445 /// <exception cref="OutOfMemoryException">Out of memory</exception>
446 /// <exception cref="IOException">Internal error</exception>
447 /// <exception cref="UnauthorizedAccessException">Not permitted</exception>
448 /// <exception cref="NotSupportedException">Not supported</exception>
449 [EditorBrowsable(EditorBrowsableState.Never)]
450 public event EventHandler<SwitchUserCompletionEventArgs> SwitchUserCompleted
454 lock (_switchUserCompletionLock)
456 if (_switchUserCompletionHandler == null)
457 RegisterCallbackForEvent(SessionEventType.SwitchUserCompletion, _switchUserCompletionCB, OnSwitchUserCompletion);
458 _switchUserCompletionHandler += value;
463 lock (_switchUserCompletionLock)
465 _switchUserCompletionHandler -= value;
466 if (_switchUserCompletionHandler == null)
467 UnregisterCallbackForEvent(SessionEventType.SwitchUserCompletion, _switchUserCompletionCB);
472 private void CheckError(SessionError ret, string msg)
474 if (ret == SessionError.None)
477 Log.Error(SessionErrorFactory.LogTag, msg);
478 Exception ex = SessionErrorFactory.CreateException(ret);
481 Log.Error(SessionErrorFactory.LogTag,
482 "Unexpected exception type for SessionError: " + Enum.GetName(typeof(SessionError), ret));
483 throw new InvalidOperationException("Unrecognized error");
488 private void IntPtrToStringArray(IntPtr unmanagedArray, int size, out string[] managedArray)
490 managedArray = new string[size];
492 byte[] byteArray = new byte[MaxUserLength * size];
493 byte[] tmpArray = new byte[MaxUserLength];
495 Marshal.Copy(unmanagedArray, byteArray, 0, MaxUserLength * size);
497 for (int i = 0; i < size; i++)
499 Array.Copy(byteArray, MaxUserLength * i, tmpArray, 0, MaxUserLength);
500 managedArray[i] = Encoding.UTF8.GetString(tmpArray);
504 private void RegisterCallbackForEvent(SessionEventType eventType, Interop.Session.SubsessionEventCallback eventCallback,
505 EventDelegate delegateToSet)
507 eventCallback = new Interop.Session.SubsessionEventCallback(delegateToSet);
508 SessionError ret = Interop.Session.SubesssionRegisterEventCallback(SessionUID, eventType,
509 eventCallback, IntPtr.Zero);
510 CheckError(ret, $"Interop failed to register a callback for an event of type {eventType}");
513 private void UnregisterCallbackForEvent(SessionEventType eventType, Interop.Session.SubsessionEventCallback eventCallback)
515 SessionError ret = Interop.Session.SubesssionUnregisterEventCallback(SessionUID, eventType);
516 CheckError(ret, $"Interop failed to unregister a callback for an event of type {eventType}");
517 eventCallback = null;