From 14540b4cc010f631fe456aef8b4e341cac7f800b Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 29 Nov 2017 15:47:43 -0500 Subject: [PATCH] support for faceid labels --- src/Android/Services/DeviceInfoService.cs | 2 +- .../Services/IDeviceInfoService.cs | 1 + src/App/Pages/Lock/LockFingerprintPage.cs | 9 +++-- src/App/Pages/Settings/SettingsPage.cs | 7 +++- src/App/Resources/AppResources.Designer.cs | 36 ++++++++++++++++++ src/App/Resources/AppResources.resx | 13 +++++++ src/UWP/Services/DeviceInfoService.cs | 1 + src/iOS.Core/Services/DeviceInfoService.cs | 16 ++++++++ src/iOS/Resources/smile.png | Bin 0 -> 2017 bytes src/iOS/Resources/smile@2x.png | Bin 0 -> 3682 bytes src/iOS/Resources/smile@3x.png | Bin 0 -> 5468 bytes 11 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/iOS/Resources/smile.png create mode 100644 src/iOS/Resources/smile@2x.png create mode 100644 src/iOS/Resources/smile@3x.png diff --git a/src/Android/Services/DeviceInfoService.cs b/src/Android/Services/DeviceInfoService.cs index 7b0c76c17..2b30f0ca4 100644 --- a/src/Android/Services/DeviceInfoService.cs +++ b/src/Android/Services/DeviceInfoService.cs @@ -46,7 +46,7 @@ namespace Bit.Android.Services public bool NfcEnabled => Utilities.NfcEnabled(); public bool HasCamera => Xamarin.Forms.Forms.Context.PackageManager.HasSystemFeature(PackageManager.FeatureCamera); public bool AutofillServiceSupported => AutofillSupported(); - + public bool HasFaceIdSupport => false; private bool AutofillSupported() { if(Build.VERSION.SdkInt < BuildVersionCodes.O) diff --git a/src/App/Abstractions/Services/IDeviceInfoService.cs b/src/App/Abstractions/Services/IDeviceInfoService.cs index e0d8df72f..6b55bdda0 100644 --- a/src/App/Abstractions/Services/IDeviceInfoService.cs +++ b/src/App/Abstractions/Services/IDeviceInfoService.cs @@ -8,5 +8,6 @@ bool NfcEnabled { get; } bool HasCamera { get; } bool AutofillServiceSupported { get; } + bool HasFaceIdSupport { get; } } } diff --git a/src/App/Pages/Lock/LockFingerprintPage.cs b/src/App/Pages/Lock/LockFingerprintPage.cs index 24c1eba3c..15dfb1754 100644 --- a/src/App/Pages/Lock/LockFingerprintPage.cs +++ b/src/App/Pages/Lock/LockFingerprintPage.cs @@ -15,6 +15,7 @@ namespace Bit.App.Pages private readonly IFingerprint _fingerprint; private readonly ISettings _settings; private readonly IAppSettingsService _appSettings; + private readonly IDeviceInfoService _deviceInfoService; private readonly bool _checkFingerprintImmediately; private DateTime? _lastAction; @@ -24,6 +25,7 @@ namespace Bit.App.Pages _fingerprint = Resolver.Resolve(); _settings = Resolver.Resolve(); _appSettings = Resolver.Resolve(); + _deviceInfoService = Resolver.Resolve(); Init(); } @@ -32,7 +34,7 @@ namespace Bit.App.Pages { var fingerprintIcon = new ExtendedButton { - Image = "fingerprint.png", + Image = _deviceInfoService.HasFaceIdSupport ? "smile.png" : "fingerprint.png", BackgroundColor = Color.Transparent, Command = new Command(async () => await CheckFingerprintAsync()), VerticalOptions = LayoutOptions.CenterAndExpand, @@ -41,7 +43,8 @@ namespace Bit.App.Pages var fingerprintButton = new ExtendedButton { - Text = AppResources.UseFingerprintToUnlock, + Text = _deviceInfoService.HasFaceIdSupport ? AppResources.UseFaceIDToUnlock : + AppResources.UseFingerprintToUnlock, Command = new Command(async () => await CheckFingerprintAsync()), VerticalOptions = LayoutOptions.EndAndExpand, Style = (Style)Application.Current.Resources["btn-primary"] @@ -64,7 +67,7 @@ namespace Bit.App.Pages Children = { fingerprintIcon, fingerprintButton, logoutButton } }; - Title = AppResources.VerifyFingerprint; + Title = _deviceInfoService.HasFaceIdSupport ? AppResources.VerifyFaceID : AppResources.VerifyFingerprint; Content = stackLayout; } diff --git a/src/App/Pages/Settings/SettingsPage.cs b/src/App/Pages/Settings/SettingsPage.cs index ad0f4a04a..2cf81aacf 100644 --- a/src/App/Pages/Settings/SettingsPage.cs +++ b/src/App/Pages/Settings/SettingsPage.cs @@ -20,6 +20,7 @@ namespace Bit.App.Pages private readonly IPushNotificationService _pushNotification; private readonly IGoogleAnalyticsService _googleAnalyticsService; private readonly IDeviceActionService _deviceActionService; + private readonly IDeviceInfoService _deviceInfoService; private readonly ILockService _lockService; // TODO: Model binding context? @@ -33,6 +34,7 @@ namespace Bit.App.Pages _pushNotification = Resolver.Resolve(); _googleAnalyticsService = Resolver.Resolve(); _deviceActionService = Resolver.Resolve(); + _deviceInfoService = Resolver.Resolve(); _lockService = Resolver.Resolve(); Init(); @@ -91,8 +93,9 @@ namespace Bit.App.Pages if((await _fingerprint.GetAvailabilityAsync()) == FingerprintAvailability.Available) { - var fingerprintName = Helpers.OnPlatform(iOS: AppResources.TouchID, Android: AppResources.Fingerprint, - Windows: AppResources.Fingerprint, WinPhone: AppResources.Fingerprint); + var fingerprintName = Helpers.OnPlatform( + iOS: _deviceInfoService.HasFaceIdSupport ? AppResources.FaceID : AppResources.TouchID, + Android: AppResources.Fingerprint, Windows: AppResources.Fingerprint, WinPhone: AppResources.Fingerprint); FingerprintCell = new ExtendedSwitchCell { Text = string.Format(AppResources.UnlockWith, fingerprintName), diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index ccf25293d..e9b5ba525 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -1303,6 +1303,24 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Face ID. + /// + public static string FaceID { + get { + return ResourceManager.GetString("FaceID", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use Face ID to verify.. + /// + public static string FaceIDDirection { + get { + return ResourceManager.GetString("FaceIDDirection", resourceCulture); + } + } + /// /// Looks up a localized string similar to Favorite. /// @@ -2905,6 +2923,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Use Face ID To Unlock. + /// + public static string UseFaceIDToUnlock { + get { + return ResourceManager.GetString("UseFaceIDToUnlock", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use Fingerprint to Unlock. /// @@ -2995,6 +3022,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Verify Face ID. + /// + public static string VerifyFaceID { + get { + return ResourceManager.GetString("VerifyFaceID", resourceCulture); + } + } + /// /// Looks up a localized string similar to Verify Fingerprint. /// diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 9178fc6df..c5d45321f 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -1218,4 +1218,17 @@ Open Autofill Settings + + Face ID + What Apple calls their facial recognition reader. + + + Use Face ID to verify. + + + Use Face ID To Unlock + + + Verify Face ID + \ No newline at end of file diff --git a/src/UWP/Services/DeviceInfoService.cs b/src/UWP/Services/DeviceInfoService.cs index c30758d67..fa8f79b92 100644 --- a/src/UWP/Services/DeviceInfoService.cs +++ b/src/UWP/Services/DeviceInfoService.cs @@ -41,5 +41,6 @@ namespace Bit.UWP.Services } public bool AutofillServiceSupported => false; + public bool HasFaceIdSupport => false; } } diff --git a/src/iOS.Core/Services/DeviceInfoService.cs b/src/iOS.Core/Services/DeviceInfoService.cs index 83ab7c924..4eb50cded 100644 --- a/src/iOS.Core/Services/DeviceInfoService.cs +++ b/src/iOS.Core/Services/DeviceInfoService.cs @@ -1,4 +1,6 @@ using Bit.App.Abstractions; +using Foundation; +using LocalAuthentication; using UIKit; namespace Bit.iOS.Core.Services @@ -25,5 +27,19 @@ namespace Bit.iOS.Core.Services public bool NfcEnabled => false; public bool HasCamera => true; public bool AutofillServiceSupported => false; + public bool HasFaceIdSupport + { + get + { + if(Version < 11) + { + return false; + } + + var context = new LAContext(); + return context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError e) && + context.BiometryType == LABiometryType.TypeFaceId; + } + } } } diff --git a/src/iOS/Resources/smile.png b/src/iOS/Resources/smile.png new file mode 100644 index 0000000000000000000000000000000000000000..25f2f45b1c9e513c403159ba8e1e9cf20a51a6bc GIT binary patch literal 2017 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 literal 0 HcmV?d00001 diff --git a/src/iOS/Resources/smile@3x.png b/src/iOS/Resources/smile@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6e7189e69c140b10692800db22501fcfc443b344 GIT binary patch literal 5468 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