Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
PlatformSpecific.cs
1using Foundation;
2using LocalAuthentication;
4using ObjCRuntime;
5using System.Diagnostics.CodeAnalysis;
6using UIKit;
7using Waher.Events;
9using Security;
10using System.Text;
11using CommunityToolkit.Mvvm.Messaging;
12using Plugin.Firebase.CloudMessaging;
13using UserNotifications;
14
16{
21 {
22 private LAContext? localAuthenticationContext;
23 private bool isDisposed;
24
29 {
30 NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, this.OnKeyboardWillShow);
31 NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, this.OnKeyboardWillHide);
32 }
33
37 public void Dispose()
38 {
39 this.Dispose(true);
40 GC.SuppressFinalize(this);
41 }
42
46 protected virtual void Dispose(bool Disposing)
47 {
48 if (this.isDisposed)
49 return;
50
51 NSNotificationCenter.DefaultCenter.RemoveObserver(UIKeyboard.WillShowNotification);
52 NSNotificationCenter.DefaultCenter.RemoveObserver(UIKeyboard.WillHideNotification);
53
54 if (Disposing)
55 this.DisposeLocalAuthenticationContext();
56
57 this.isDisposed = true;
58 }
59
63 public bool CanProhibitScreenCapture => false;
64
68 public bool ProhibitScreenCapture // iOS doesn't support screen protection
69 {
70 get => false;
71 set => _ = value; // ignore the value
72 }
73
79 public string? GetDeviceId()
80 {
81 try
82 {
83 string ServiceName = AppInfo.PackageName;
84 const string AccountName = "DeviceIdentifier"; //Basically the key
85
86 // Define the search criteria for the SecRecord
87 SecRecord searchRecord = new(SecKind.GenericPassword)
88 {
89 Service = ServiceName,
90 Account = AccountName
91 };
92
93 // Try to retrieve the existing device identifier from the Keychain
94 SecRecord? existingRecord = SecKeyChain.QueryAsRecord(searchRecord, out SecStatusCode resultCode);
95 if (resultCode == SecStatusCode.Success && existingRecord is not null && existingRecord?.ValueData is not null)
96 {
97 // If the record exists, return the identifier
98 return existingRecord.ValueData.ToString(NSStringEncoding.UTF8);
99 }
100 else if (resultCode == SecStatusCode.ItemNotFound)
101 {
102 // No existing record found, create a new device identifier
103 string identifier = UIDevice.CurrentDevice.IdentifierForVendor.ToString();
104
105 // Define the SecRecord for storing the new identifier
106 SecRecord newRecord = new(SecKind.GenericPassword)
107 {
108 Service = ServiceName,
109 Account = AccountName,
110 Label = "Persistent Device Identifier for Vendor",
111 ValueData = NSData.FromString(identifier),
112 Accessible = SecAccessible.WhenUnlockedThisDeviceOnly,
113 Synchronizable = false
114 };
115
116 // Sanity check: Remove any existing record, which should not exist
117 SecKeyChain.Remove(newRecord);
118
119 // Add the new item to the Keychain
120 SecStatusCode addResult = SecKeyChain.Add(newRecord);
121 if (addResult == SecStatusCode.Success)
122 return identifier; // Return the newly stored identifier
123
124 throw new Exception($"Unable to store device identifier in Keychain - Code: {addResult} - Description: {SecStatusCodeExtensions.GetStatusDescription(addResult)}");
125 }
126 else
127 throw new Exception($"Unable to retrieve device identifier from Keychain - Code: {resultCode} - Description: {SecStatusCodeExtensions.GetStatusDescription(resultCode)}");
128 }
129 catch (Exception ex)
130 {
131 try
132 {
135
136 StringBuilder msg = new();
137
138 msg.Append(ex.Message);
139 msg.AppendLine("\n\n");
140 msg.AppendLine("```");
141 msg.AppendLine(ex.StackTrace);
142 msg.AppendLine("```");
143
144 App.SendAlertAsync(msg.ToString(), "text/plain").Wait();
145 this.CloseApplication().Wait();
146 }
147 catch (Exception)
148 {
149 Environment.Exit(0);
150 }
151 }
152 return null;
153 }
154
158 public Task CloseApplication()
159 {
160 if (this.localAuthenticationContext is not null)
161 {
162 if (this.localAuthenticationContext.RespondsToSelector(new Selector("invalidate")))
163 this.localAuthenticationContext.Invalidate();
164
165 this.localAuthenticationContext.Dispose();
166 this.localAuthenticationContext = null;
167 }
168
169 Environment.Exit(0);
170 return Task.CompletedTask;
171 }
172
180 public void ShareImage(byte[] PngFile, string Message, string Title, string FileName)
181 {
182 UIImage? ImageObject = UIImage.LoadFromData(NSData.FromArray(PngFile));
183 UIWindow? KeyWindow = UIApplication.SharedApplication?.KeyWindow;
184
185 if ((ImageObject is null) || (KeyWindow is null))
186 return;
187
188 NSString MessageObject = new(Message);
189 NSObject[] Items = [MessageObject, ImageObject];
190 UIActivityViewController activityController = new(Items, null);
191
192 UIViewController? topController = KeyWindow.RootViewController;
193
194 if (topController is not null)
195 {
196 while (topController.PresentedViewController is not null)
197 topController = topController.PresentedViewController;
198
199 topController.PresentViewController(activityController, true, () => { });
200 }
201 }
202
203 /*
208 public Task<byte[]> CaptureScreen(int blurRadius = 25)
209 {
210 blurRadius = Math.Min(25, Math.Max(blurRadius, 0));
211 UIImage? capture;
212
213 using UIBlurEffect blurEffect = UIBlurEffect.FromStyle(UIBlurEffectStyle.Regular);
214 using UIVisualEffectView blurWindow = new(blurEffect);
215
216 blurWindow.Frame = UIScreen.MainScreen.Bounds;
217 blurWindow.Alpha = Math.Min(1.0f, (1.0f / 25.0f) * blurRadius);
218
219 UIView? subview = UIScreen.MainScreen.SnapshotView(true);
220 //capture = UIScreen.MainScreen.Capture();
221 //var subview = new UIImageView(capture);
222 subview?.AddSubview(blurWindow);
223 capture = subview?.Capture(true);
224 blurWindow.RemoveFromSuperview();
225 subview?.Dispose();
226
228 return Task.FromResult(Array.Empty<byte>());
229 }
230 */
235 {
236 get
237 {
238 if (!this.HasLocalAuthenticationContext)
239 return false;
240
241 try
242 {
243 if (!this.localAuthenticationContext.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out _))
244 return false;
245
246 return true;
247 }
248 catch (Exception ex)
249 {
250 ServiceRef.LogService.LogException(ex);
251 return false;
252 }
253 }
254 }
255
262 {
263 if (!this.HasLocalAuthenticationContext)
264 return BiometricMethod.None;
265
266 try
267 {
268 if (!this.localAuthenticationContext.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out _))
269 return BiometricMethod.None;
270
271 return this.localAuthenticationContext.BiometryType switch
272 {
273 LABiometryType.FaceId => BiometricMethod.FaceId,
274 LABiometryType.TouchId => BiometricMethod.TouchId,
275 _ => BiometricMethod.Unknown
276 };
277 }
278 catch (Exception ex)
279 {
280 ServiceRef.LogService.LogException(ex);
281 return BiometricMethod.Unknown;
282 }
283 }
284
285 [MemberNotNullWhen(true, nameof(localAuthenticationContext))]
286 private bool HasLocalAuthenticationContext
287 {
288 get
289 {
290 try
291 {
292 if (this.localAuthenticationContext is null)
293 {
294 NSProcessInfo ProcessInfo = new();
295 NSOperatingSystemVersion MinVersion = new(10, 12, 0);
296 if (!ProcessInfo.IsOperatingSystemAtLeastVersion(MinVersion))
297 return false;
298
299 if (!UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
300 return false;
301
302 if (Class.GetHandle(typeof(LAContext)) == IntPtr.Zero)
303 return false;
304
305 this.localAuthenticationContext = new LAContext();
306 }
307
308 return true;
309 }
310 catch (Exception ex)
311 {
312 ServiceRef.LogService.LogException(ex);
313 return false;
314 }
315 }
316 }
317
318 private void DisposeLocalAuthenticationContext()
319 {
320 if (this.localAuthenticationContext is not null)
321 {
322 if (this.localAuthenticationContext.RespondsToSelector(new Selector("invalidate")))
323 this.localAuthenticationContext.Invalidate();
324
325 this.localAuthenticationContext.Dispose();
326 this.localAuthenticationContext = null;
327 }
328 }
329
340 public async Task<bool> AuthenticateUserFingerprint(string Title, string? Subtitle, string Description, string Cancel,
341 CancellationToken? CancellationToken)
342 {
343 if (!this.HasLocalAuthenticationContext)
344 return false;
345
346 CancellationTokenRegistration? Registration = null;
347
348 try
349 {
350 if (this.localAuthenticationContext.RespondsToSelector(new Selector("localizedFallbackTitle")))
351 this.localAuthenticationContext.LocalizedFallbackTitle = Title;
352
353 if (this.localAuthenticationContext.RespondsToSelector(new Selector("localizedCancelTitle")))
354 this.localAuthenticationContext.LocalizedCancelTitle = Cancel;
355
356 Registration = CancellationToken?.Register(this.DisposeLocalAuthenticationContext);
357
358 (bool Success, NSError _) = await this.localAuthenticationContext.EvaluatePolicyAsync(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, Description);
359
360 this.DisposeLocalAuthenticationContext();
361
362 return Success;
363 }
364 catch (Exception ex)
365 {
366 ServiceRef.LogService.LogException(ex);
367 return false;
368 }
369 finally
370 {
371 if (Registration.HasValue)
372 Registration.Value.Dispose();
373 }
374 }
375
380 public async Task<TokenInformation> GetPushNotificationToken()
381 {
382 string Token = string.Empty;
383
384 try
385 {
386 await CrossFirebaseCloudMessaging.Current.CheckIfValidAsync();
387 Token = await CrossFirebaseCloudMessaging.Current.GetTokenAsync();
388 }
389 catch (Exception ex)
390 {
391 Log.Exception(ex);
392 }
393
395 {
396 Token = Token,
398 Service = PushMessagingService.Firebase
399 };
400
401 return TokenInformation;
402 }
403
404 #region Keyboard
405 public event EventHandler<KeyboardSizeMessage>? KeyboardShown;
406 public event EventHandler<KeyboardSizeMessage>? KeyboardHidden;
407 public event EventHandler<KeyboardSizeMessage>? KeyboardSizeChanged;
408
409
413 public void HideKeyboard()
414 {
415 AppDelegate.GetKeyWindow()?.EndEditing(true);
416 }
417
418 private void OnKeyboardWillShow(NSNotification notification)
419 {
420 CoreGraphics.CGRect keyboardFrame = UIKeyboard.FrameEndFromNotification(notification);
421 float keyboardHeight = (float)keyboardFrame.Height;
422 KeyboardShown.Raise(this, new KeyboardSizeMessage(keyboardHeight));
423 KeyboardSizeChanged.Raise(this, new KeyboardSizeMessage(keyboardHeight));
424 WeakReferenceMessenger.Default.Send(new KeyboardSizeMessage(keyboardHeight));
425 }
426
427 private void OnKeyboardWillHide(NSNotification notification)
428 {
429 float keyboardHeight = 0;
430 KeyboardHidden.Raise(this, new KeyboardSizeMessage(keyboardHeight));
431 KeyboardSizeChanged.Raise(this, new KeyboardSizeMessage(keyboardHeight));
432 WeakReferenceMessenger.Default.Send(new KeyboardSizeMessage(keyboardHeight));
433 }
434
435
436 #endregion
437
438 #region Notifications
445 private async void ShowLocalNotification(string title, string body, IDictionary<string, string> data)
446 {
447 try
448 {
449 // Check current notification settings without prompting the user
450 UNNotificationSettings Settings = await UNUserNotificationCenter.Current.GetNotificationSettingsAsync();
451 if (Settings.AuthorizationStatus != UNAuthorizationStatus.Authorized)
452 return;
453
454 // Create the notification content
455 UNMutableNotificationContent Content = new UNMutableNotificationContent
456 {
457 Title = title,
458 Body = body,
459 Sound = UNNotificationSound.Default
460 };
461
462 // Add any additional data as UserInfo
463 if (data is not null)
464 {
465 NSMutableDictionary UserInfo = new();
466 foreach (KeyValuePair<string, string> Pair in data)
467 {
468 UserInfo.SetValueForKey(new NSString(Pair.Value), new NSString(Pair.Key));
469 }
470 Content.UserInfo = UserInfo;
471 }
472
473 // Schedule the notification after a short delay
474 UNTimeIntervalNotificationTrigger Trigger = UNTimeIntervalNotificationTrigger.CreateTrigger(1, false);
475 UNNotificationRequest Request = UNNotificationRequest.FromIdentifier(Guid.NewGuid().ToString(), Content, Trigger);
476 UNUserNotificationCenter.Current.AddNotificationRequest(Request, error =>
477 {
478 if (error is not null)
479 {
480 ServiceRef.LogService.LogWarning($"Error scheduling notification: {error.LocalizedDescription}");
481 }
482 });
483
484 }
485 catch (Exception ex)
486 {
487 return;
488 }
489 }
490
491 // The following methods use the helper above.
492 public void ShowMessageNotification(string Title, string MessageBody, IDictionary<string, string> Data)
493 {
494 this.ShowLocalNotification(Title, MessageBody, Data);
495 }
496
497 public void ShowIdentitiesNotification(string Title, string MessageBody, IDictionary<string, string> Data)
498 {
499 this.ShowLocalNotification(Title, MessageBody, Data);
500 }
501
502 public void ShowPetitionNotification(string Title, string MessageBody, IDictionary<string, string> Data)
503 {
504 this.ShowLocalNotification(Title, MessageBody, Data);
505 }
506
507 public void ShowContractsNotification(string Title, string MessageBody, IDictionary<string, string> Data)
508 {
509 this.ShowLocalNotification(Title, MessageBody, Data);
510 }
511
512 public void ShowEDalerNotification(string Title, string MessageBody, IDictionary<string, string> Data)
513 {
514 this.ShowLocalNotification(Title, MessageBody, Data);
515 }
516
517 public void ShowTokenNotification(string Title, string MessageBody, IDictionary<string, string> Data)
518 {
519 this.ShowLocalNotification(Title, MessageBody, Data);
520 }
521
522 public void ShowProvisioningNotification(string Title, string MessageBody, IDictionary<string, string> Data)
523 {
524 this.ShowLocalNotification(Title, MessageBody, Data);
525 }
526 #endregion
527
528 public Thickness GetInsets()
529 {
536 // Try to get the current UIWindow.
537 UIWindow? Window = AppDelegate.GetKeyWindow();
538 if (Window is null)
539 return new Thickness(0);
540
541 // Prefer using the root view's SafeAreaLayoutGuide to compute insets.
542 UIView? RootView = Window.RootViewController?.View;
543 if (RootView is not null)
544 {
545 CoreGraphics.CGRect Bounds = RootView.Bounds;
546 CoreGraphics.CGRect LayoutFrame = RootView.SafeAreaLayoutGuide.LayoutFrame;
547
548 double Left = LayoutFrame.Left - Bounds.Left;
549 double Top = LayoutFrame.Top - Bounds.Top;
550 double Right = Bounds.Right - LayoutFrame.Right;
551 double Bottom = Bounds.Bottom - LayoutFrame.Bottom;
552
553 // Validate non-negative before returning.
554 if (Left >= 0 && Top >= 0 && Right >= 0 && Bottom >= 0)
555 return new Thickness(Left, Top, Right, Bottom);
556 }
557
558 // Fallback to window safe area insets.
559 UIEdgeInsets Insets = Window.SafeAreaInsets;
560 return new Thickness(
561 (double)Insets.Left,
562 (double)Insets.Top,
563 (double)Insets.Right,
564 (double)Insets.Bottom);
565 }
566 }
567}
Represents an instance of the Neuro-Access app.
Definition: App.xaml.cs:125
Android implementation of platform-specific features.
EventHandler< KeyboardSizeMessage >? KeyboardShown
bool CanProhibitScreenCapture
If screen capture prohibition is supported
void ShareImage(byte[] PngFile, string Message, string Title, string FileName)
Shares an image in PNG format.
PlatformSpecific()
iOS implementation of platform-specific features.
void HideKeyboard()
Force hide the keyboard
bool SupportsFingerprintAuthentication
If the device supports authenticating the user using fingerprints.
BiometricMethod GetBiometricMethod()
Gets the biometric method supported by the device. Can return Face, Fingerprint, Unknown,...
async Task< bool > AuthenticateUserFingerprint(string Title, string? Subtitle, string Description, string Cancel, CancellationToken? CancellationToken)
Authenticates the user using the fingerprint sensor.
virtual void Dispose(bool Disposing)
IDisposable.Dispose
EventHandler< KeyboardSizeMessage >? KeyboardSizeChanged
Fired when the keyboard size changes.
string? GetDeviceId()
Gets A persistent ID of the device Fetches the device ID from the keychain, or creates a new one if i...
async Task< TokenInformation > GetPushNotificationToken()
Gets a Push Notification token for the device.
EventHandler< KeyboardSizeMessage >? KeyboardHidden
bool ProhibitScreenCapture
If screen capture is prohibited or not.
Thickness GetInsets()
Gets the safe area insets for the device (top, bottom, left, right).
Task CloseApplication()
Closes the application
Contains information about a push notification token.
Base class that references services in the app.
Definition: ServiceRef.cs:43
static ILogService LogService
Log service.
Definition: ServiceRef.cs:214
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
Interface for platform-specific functions.
Definition: ImplTypes.g.cs:58
BiometricMethod
Enum representing the device biometric method for authentication.
class KeyboardSizeMessage(float KeyboardSize)
Keyboard size change message
Definition: Messages.cs:32
ClientType
Type of client requesting notification.
Definition: ClientType.cs:7