From d4bf4705a475f3b58b65685db02931da7b52a73c Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Wed, 29 Jan 2020 13:00:32 -0600 Subject: [PATCH 01/65] Add generic local authentication method --- .../ios/Classes/FLTLocalAuthPlugin.m | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m index b9925e3f425d..5d3229ba7018 100644 --- a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m @@ -21,6 +21,8 @@ + (void)registerWithRegistrar:(NSObject *)registrar { - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"authenticateWithBiometrics" isEqualToString:call.method]) { [self authenticateWithBiometrics:call.arguments withFlutterResult:result]; + } else if ([@"authenticate" isEqualToString:call.method]) { + [self authenticate:call.arguments withFlutterResult:result]; } else if ([@"getAvailableBiometrics" isEqualToString:call.method]) { [self getAvailableBiometrics:result]; } else { @@ -127,6 +129,46 @@ - (void)authenticateWithBiometrics:(NSDictionary *)arguments } } +- (void)authenticate:(NSDictionary *)arguments + withFlutterResult:(FlutterResult)result { + LAContext *context = [[LAContext alloc] init]; + NSError *authError = nil; + lastCallArgs = nil; + lastResult = nil; + context.localizedFallbackTitle = @""; + + if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication + error:&authError]) { + [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication + localizedReason:arguments[@"localizedReason"] + reply:^(BOOL success, NSError *error) { + if (success) { + result(@YES); + } else { + switch (error.code) { + case LAErrorPasscodeNotSet: + case LAErrorTouchIDNotAvailable: + case LAErrorTouchIDNotEnrolled: + case LAErrorTouchIDLockout: + [self handleErrors:error + flutterArguments:arguments + withFlutterResult:result]; + return; + case LAErrorSystemCancel: + if ([arguments[@"stickyAuth"] boolValue]) { + lastCallArgs = arguments; + lastResult = result; + return; + } + } + result(@NO); + } + }]; + } else { + [self handleErrors:authError flutterArguments:arguments withFlutterResult:result]; + } +} + - (void)handleErrors:(NSError *)authError flutterArguments:(NSDictionary *)arguments withFlutterResult:(FlutterResult)result { From 624e178a402411a48e00674bdff2677f93690236 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 09:07:19 -0600 Subject: [PATCH 02/65] Android failover to device authentication --- .../localauth/AuthenticationHelper.java | 7 +- .../plugins/localauth/LocalAuthPlugin.java | 170 ++++++++++-------- 2 files changed, 97 insertions(+), 80 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index e907cc43f2b4..156a7884c235 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -12,7 +12,6 @@ import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.view.ContextThemeWrapper; @@ -75,7 +74,8 @@ interface AuthCompletionHandler { Lifecycle lifecycle, FragmentActivity activity, MethodCall call, - AuthCompletionHandler completionHandler) { + AuthCompletionHandler completionHandler, + boolean failOverToDeviceAuth) { this.lifecycle = lifecycle; this.activity = activity; this.completionHandler = completionHandler; @@ -87,8 +87,9 @@ interface AuthCompletionHandler { .setDescription((String) call.argument("localizedReason")) .setTitle((String) call.argument("signInTitle")) .setSubtitle((String) call.argument("fingerprintHint")) - .setNegativeButtonText((String) call.argument("cancelButton")) + .setNegativeButtonText(failOverToDeviceAuth ? null : (String) call.argument("cancelButton")) .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) + .setDeviceCredentialAllowed(failOverToDeviceAuth) .build(); } diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 1f3d35f44bfa..503909364c8d 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -46,8 +46,8 @@ public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, Activi * initialized this way won't react to changes in activity or context. * * @param registrar attaches this plugin's {@link - * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to the registrar's {@link - * io.flutter.plugin.common.BinaryMessenger}. + * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to the registrar's {@link + * io.flutter.plugin.common.BinaryMessenger}. */ public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); @@ -67,93 +67,82 @@ public LocalAuthPlugin() {} @Override public void onMethodCall(MethodCall call, final Result result) { - if (call.method.equals("authenticateWithBiometrics")) { - if (authInProgress.get()) { - // Apps should not invoke another authentication request while one is in progress, - // so we classify this as an error condition. If we ever find a legitimate use case for - // this, we can try to cancel the ongoing auth and start a new one but for now, not worth - // the complexity. - result.error("auth_in_progress", "Authentication in progress", null); - return; - } + switch (call.method) { + case "authenticate": + case "authenticateWithBiometrics": + this.authenticate(call, result); + break; + case "getAvailableBiometrics": + this.getAvailableBiometrics(result); + break; + case "stopAuthentication": + this.stopAuthentication(result); + break; + default: + result.notImplemented(); + break; + } + } - if (activity == null || activity.isFinishing()) { - result.error("no_activity", "local_auth plugin requires a foreground activity", null); - return; - } + private void authenticate(MethodCall call, final Result result) { + if (authInProgress.get()) { + // Apps should not invoke another authentication request while one is in progress, + // so we classify this as an error condition. If we ever find a legitimate use case for + // this, we can try to cancel the ongoing auth and start a new one but for now, not worth + // the complexity. + result.error("auth_in_progress", "Authentication in progress", null); + return; + } - if (!(activity instanceof FragmentActivity)) { - result.error( - "no_fragment_activity", - "local_auth plugin requires activity to be a FragmentActivity.", - null); - return; - } - authInProgress.set(true); - authenticationHelper = - new AuthenticationHelper( - lifecycle, - (FragmentActivity) activity, - call, - new AuthCompletionHandler() { - @Override - public void onSuccess() { - if (authInProgress.compareAndSet(true, false)) { - result.success(true); - } + if (activity == null || activity.isFinishing()) { + result.error("no_activity", "local_auth plugin requires a foreground activity", null); + return; + } + + if (!(activity instanceof FragmentActivity)) { + result.error( + "no_fragment_activity", + "local_auth plugin requires activity to be a FragmentActivity.", + null); + return; + } + + authInProgress.set(true); + boolean failoverToDeviceAuth = call.method.equals("authenticate"); + authenticationHelper = new AuthenticationHelper( + lifecycle, + (FragmentActivity) activity, + call, + new AuthCompletionHandler() { + @Override + public void onSuccess() { + if (authInProgress.compareAndSet(true, false)) { + result.success(true); } + } - @Override - public void onFailure() { - if (authInProgress.compareAndSet(true, false)) { - result.success(false); - } + @Override + public void onFailure() { + if (authInProgress.compareAndSet(true, false)) { + result.success(false); } + } - @Override - public void onError(String code, String error) { - if (authInProgress.compareAndSet(true, false)) { - result.error(code, error, null); - } + @Override + public void onError(String code, String error) { + if (authInProgress.compareAndSet(true, false)) { + result.error(code, error, null); } - }); - authenticationHelper.authenticate(); - } else if (call.method.equals("getAvailableBiometrics")) { - try { - if (activity == null || activity.isFinishing()) { - result.error("no_activity", "local_auth plugin requires a foreground activity", null); - return; - } - ArrayList biometrics = new ArrayList(); - PackageManager packageManager = activity.getPackageManager(); - if (Build.VERSION.SDK_INT >= 23) { - if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { - biometrics.add("fingerprint"); - } - } - if (Build.VERSION.SDK_INT >= 29) { - if (packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) { - biometrics.add("face"); - } - if (packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)) { - biometrics.add("iris"); - } - } - result.success(biometrics); - } catch (Exception e) { - result.error("no_biometrics_available", e.getMessage(), null); - } - } else if (call.method.equals(("stopAuthentication"))) { - stopAuthentication(result); - } else { - result.notImplemented(); - } + } + }, + failoverToDeviceAuth); + authenticationHelper.authenticate(); } /* Stops the authentication if in progress. */ - private void stopAuthentication(Result result) { + private void stopAuthentication(final Result result) { try { if (authenticationHelper != null && authInProgress.get()) { authenticationHelper.stopAuthentication(); @@ -167,6 +156,33 @@ private void stopAuthentication(Result result) { } } + private void getAvailableBiometrics(final Result result) { + try { + if (activity == null || activity.isFinishing()) { + result.error("no_activity", "local_auth plugin requires a foreground activity", null); + return; + } + ArrayList biometrics = new ArrayList(); + PackageManager packageManager = activity.getPackageManager(); + if (Build.VERSION.SDK_INT >= 23) { + if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + biometrics.add("fingerprint"); + } + } + if (Build.VERSION.SDK_INT >= 29) { + if (packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) { + biometrics.add("face"); + } + if (packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)) { + biometrics.add("iris"); + } + } + result.success(biometrics); + } catch (Exception e) { + result.error("no_biometrics_available", e.getMessage(), null); + } + } + @Override public void onAttachedToEngine(FlutterPluginBinding binding) { channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), CHANNEL_NAME); From 30c9e069fd4f6a72b9a4e1b6b7b1f40fd38ec36a Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 09:08:41 -0600 Subject: [PATCH 03/65] Add authenticate method that fails over to device authentication --- packages/local_auth/lib/local_auth.dart | 39 ++++++++++++++++++++----- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index b2b03b920d64..5eaa9a124b78 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -92,8 +92,36 @@ class LocalAuthentication { 'operating systems.', details: 'Your operating system is ${_platform.operatingSystem}'); } - return await _channel.invokeMethod( - 'authenticateWithBiometrics', args); + return await _channel.invokeMethod('authenticateWithBiometrics', args); + } + + Future authenticate({ + @required String localizedReason, + bool useErrorDialogs = true, + bool stickyAuth = false, + AndroidAuthMessages androidAuthStrings = const AndroidAuthMessages(), + IOSAuthMessages iOSAuthStrings = const IOSAuthMessages(), + bool sensitiveTransaction = true, + }) async { + assert(localizedReason != null); + final Map args = { + 'localizedReason': localizedReason, + 'useErrorDialogs': useErrorDialogs, + 'stickyAuth': stickyAuth, + 'sensitiveTransaction': sensitiveTransaction, + }; + if (_platform.isIOS) { + args.addAll(iOSAuthStrings.args); + } else if (_platform.isAndroid) { + args.addAll(androidAuthStrings.args); + } else { + throw PlatformException( + code: otherOperatingSystem, + message: 'Local authentication does not support non-Android/iOS operating systems.', + details: 'Your operating system is ${_platform.operatingSystem}', + ); + } + return await _channel.invokeMethod('authenticate', args); } /// Returns true if auth was cancelled successfully. @@ -111,9 +139,7 @@ class LocalAuthentication { /// Returns true if device is capable of checking biometrics /// /// Returns a [Future] bool true or false: - Future get canCheckBiometrics async => - (await _channel.invokeListMethod('getAvailableBiometrics')) - .isNotEmpty; + Future get canCheckBiometrics async => (await _channel.invokeListMethod('getAvailableBiometrics')).isNotEmpty; /// Returns a list of enrolled biometrics /// @@ -122,8 +148,7 @@ class LocalAuthentication { /// - BiometricType.fingerprint /// - BiometricType.iris (not yet implemented) Future> getAvailableBiometrics() async { - final List result = - (await _channel.invokeListMethod('getAvailableBiometrics')); + final List result = (await _channel.invokeListMethod('getAvailableBiometrics')); final List biometrics = []; result.forEach((String value) { switch (value) { From 074c9ff4a5a914db31e3c3449ae26c9367bd29da Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 09:08:54 -0600 Subject: [PATCH 04/65] Update example --- packages/local_auth/example/lib/main.dart | 116 ++++++++++++++++------ 1 file changed, 87 insertions(+), 29 deletions(-) diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index 06e33b9853be..6beb515ab38f 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -61,10 +61,27 @@ class _MyAppState extends State { _isAuthenticating = true; _authorized = 'Authenticating'; }); - authenticated = await auth.authenticateWithBiometrics( - localizedReason: 'Scan your fingerprint to authenticate', - useErrorDialogs: true, - stickyAuth: true); + authenticated = await auth.authenticate(localizedReason: 'Let OS determine authentication method', useErrorDialogs: true, stickyAuth: true); + setState(() { + _isAuthenticating = false; + _authorized = 'Authenticating'; + }); + } on PlatformException catch (e) { + print(e); + } + if (!mounted) return; + + setState(() => _authorized = authenticated ? 'Authorized' : 'Not Authorized'); + } + + Future _authenticateWithBiometrics() async { + bool authenticated = false; + try { + setState(() { + _isAuthenticating = true; + _authorized = 'Authenticating'; + }); + authenticated = await auth.authenticateWithBiometrics(localizedReason: 'Scan your fingerprint to authenticate', useErrorDialogs: true, stickyAuth: true); setState(() { _isAuthenticating = false; _authorized = 'Authenticating'; @@ -80,39 +97,80 @@ class _MyAppState extends State { }); } - void _cancelAuthentication() { - auth.stopAuthentication(); + void _cancelAuthentication() async { + await auth.stopAuthentication(); + setState(() => _isAuthenticating = false); } @override Widget build(BuildContext context) { return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), - body: ConstrainedBox( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: ConstrainedBox( constraints: const BoxConstraints.expand(), child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text('Can check biometrics: $_canCheckBiometrics\n'), - RaisedButton( - child: const Text('Check biometrics'), - onPressed: _checkBiometrics, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Can check biometrics: $_canCheckBiometrics\n'), + RaisedButton( + child: const Text('Check biometrics'), + onPressed: _checkBiometrics, + ), + Divider(height: 100), + Text('Available biometrics: $_availableBiometrics\n'), + RaisedButton( + child: const Text('Get available biometrics'), + onPressed: _getAvailableBiometrics, + ), + Divider(height: 100), + Text('Current State: $_authorized\n'), + Visibility( + visible: !_isAuthenticating, + child: Column( + children: [ + RaisedButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Authenticate'), + Icon(Icons.perm_device_information), + ], + ), + onPressed: _authenticate, + ), + RaisedButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(_isAuthenticating ? 'Cancel' : 'Authenticate: biometrics only'), + Icon(Icons.fingerprint), + ], + ), + onPressed: _authenticateWithBiometrics, + ), + ], ), - Text('Available biometrics: $_availableBiometrics\n'), - RaisedButton( - child: const Text('Get available biometrics'), - onPressed: _getAvailableBiometrics, + ), + Visibility( + visible: _isAuthenticating, + child: RaisedButton( + onPressed: _cancelAuthentication, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Cancel Authentication"), + Icon(Icons.cancel), + ], + ), ), - Text('Current State: $_authorized\n'), - RaisedButton( - child: Text(_isAuthenticating ? 'Cancel' : 'Authenticate'), - onPressed: - _isAuthenticating ? _cancelAuthentication : _authenticate, - ) - ])), - )); + ), + ], + ), + ), + ), + ); } } From 706b5d328db0dcffdbdc87ba75796fd67b5767e6 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 10:19:58 -0600 Subject: [PATCH 05/65] Create prompt with fail over if fail over is true --- .../localauth/AuthenticationHelper.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index 156a7884c235..507c16dbbf65 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -12,6 +12,7 @@ import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; +import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.view.ContextThemeWrapper; @@ -82,15 +83,26 @@ interface AuthCompletionHandler { this.call = call; this.isAuthSticky = call.argument("stickyAuth"); this.uiThreadExecutor = new UiThreadExecutor(); - this.promptInfo = + + if(failOverToDeviceAuth){ + this.promptInfo = new BiometricPrompt.PromptInfo.Builder() .setDescription((String) call.argument("localizedReason")) .setTitle((String) call.argument("signInTitle")) .setSubtitle((String) call.argument("fingerprintHint")) - .setNegativeButtonText(failOverToDeviceAuth ? null : (String) call.argument("cancelButton")) .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) - .setDeviceCredentialAllowed(failOverToDeviceAuth) + .setDeviceCredentialAllowed(true) .build(); + } else { + this.promptInfo = + new BiometricPrompt.PromptInfo.Builder() + .setDescription((String) call.argument("localizedReason")) + .setTitle((String) call.argument("signInTitle")) + .setSubtitle((String) call.argument("fingerprintHint")) + .setNegativeButtonText((String) call.argument("cancelButton")) + .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) + .build(); + } } /** Start the fingerprint listener. */ From 1673242caf5c998a88eae38ff730c75929f7c690 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 10:27:20 -0600 Subject: [PATCH 06/65] Update example --- packages/local_auth/example/lib/main.dart | 112 +++++++++++----------- 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index 6beb515ab38f..ba74ab337116 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -109,66 +109,68 @@ class _MyAppState extends State { appBar: AppBar( title: const Text('Plugin example app'), ), - body: ConstrainedBox( - constraints: const BoxConstraints.expand(), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Can check biometrics: $_canCheckBiometrics\n'), - RaisedButton( - child: const Text('Check biometrics'), - onPressed: _checkBiometrics, - ), - Divider(height: 100), - Text('Available biometrics: $_availableBiometrics\n'), - RaisedButton( - child: const Text('Get available biometrics'), - onPressed: _getAvailableBiometrics, - ), - Divider(height: 100), - Text('Current State: $_authorized\n'), - Visibility( - visible: !_isAuthenticating, - child: Column( - children: [ - RaisedButton( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('Authenticate'), - Icon(Icons.perm_device_information), - ], - ), - onPressed: _authenticate, - ), - RaisedButton( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(_isAuthenticating ? 'Cancel' : 'Authenticate: biometrics only'), - Icon(Icons.fingerprint), - ], - ), - onPressed: _authenticateWithBiometrics, - ), - ], + body: ListView( + padding: const EdgeInsets.only(top: 30), + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Can check biometrics: $_canCheckBiometrics\n'), + RaisedButton( + child: const Text('Check biometrics'), + onPressed: _checkBiometrics, + ), + Divider(height: 100), + Text('Available biometrics: $_availableBiometrics\n'), + RaisedButton( + child: const Text('Get available biometrics'), + onPressed: _getAvailableBiometrics, ), - ), - Visibility( - visible: _isAuthenticating, - child: RaisedButton( - onPressed: _cancelAuthentication, - child: Row( - mainAxisSize: MainAxisSize.min, + Divider(height: 100), + Text('Current State: $_authorized\n'), + Visibility( + visible: !_isAuthenticating, + child: Column( children: [ - Text("Cancel Authentication"), - Icon(Icons.cancel), + RaisedButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Authenticate'), + Icon(Icons.perm_device_information), + ], + ), + onPressed: _authenticate, + ), + RaisedButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(_isAuthenticating ? 'Cancel' : 'Authenticate: biometrics only'), + Icon(Icons.fingerprint), + ], + ), + onPressed: _authenticateWithBiometrics, + ), ], ), ), - ), - ], - ), + Visibility( + visible: _isAuthenticating, + child: RaisedButton( + onPressed: _cancelAuthentication, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Cancel Authentication"), + Icon(Icons.cancel), + ], + ), + ), + ), + ], + ), + ], ), ), ); From 589c61f383367530ed797a2f32631070aeee42ee Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 10:45:34 -0600 Subject: [PATCH 07/65] Update tests --- packages/local_auth/test/local_auth_test.dart | 161 ++++++++++++------ 1 file changed, 109 insertions(+), 52 deletions(-) diff --git a/packages/local_auth/test/local_auth_test.dart b/packages/local_auth/test/local_auth_test.dart index 205c5f785708..579c541d6f56 100644 --- a/packages/local_auth/test/local_auth_test.dart +++ b/packages/local_auth/test/local_auth_test.dart @@ -30,61 +30,118 @@ void main() { log.clear(); }); - test('authenticate with no args on Android.', () async { - setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); - await localAuthentication.authenticateWithBiometrics( - localizedReason: 'Needs secure'); - expect( - log, - [ - isMethodCall('authenticateWithBiometrics', - arguments: { - 'localizedReason': 'Needs secure', - 'useErrorDialogs': true, - 'stickyAuth': false, - 'sensitiveTransaction': true, - }..addAll(const AndroidAuthMessages().args)), - ], - ); - }); + group("With device auth fail over", () { + test('authenticate with no args on Android.', () async { + setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); + await localAuthentication.authenticateWithBiometrics(localizedReason: 'Needs secure'); + expect( + log, + [ + isMethodCall('authenticateWithBiometrics', + arguments: { + 'localizedReason': 'Needs secure', + 'useErrorDialogs': true, + 'stickyAuth': false, + 'sensitiveTransaction': true, + }..addAll(const AndroidAuthMessages().args)), + ], + ); + }); - test('authenticate with no args on iOS.', () async { - setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); - await localAuthentication.authenticateWithBiometrics( - localizedReason: 'Needs secure'); - expect( - log, - [ - isMethodCall('authenticateWithBiometrics', - arguments: { - 'localizedReason': 'Needs secure', - 'useErrorDialogs': true, - 'stickyAuth': false, - 'sensitiveTransaction': true, - }..addAll(const IOSAuthMessages().args)), - ], - ); + test('authenticate with no args on iOS.', () async { + setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); + await localAuthentication.authenticateWithBiometrics(localizedReason: 'Needs secure'); + expect( + log, + [ + isMethodCall('authenticateWithBiometrics', + arguments: { + 'localizedReason': 'Needs secure', + 'useErrorDialogs': true, + 'stickyAuth': false, + 'sensitiveTransaction': true, + }..addAll(const IOSAuthMessages().args)), + ], + ); + }); + + test('authenticate with no sensitive transaction.', () async { + setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); + await localAuthentication.authenticateWithBiometrics( + localizedReason: 'Insecure', + sensitiveTransaction: false, + useErrorDialogs: false, + ); + expect( + log, + [ + isMethodCall('authenticateWithBiometrics', + arguments: { + 'localizedReason': 'Insecure', + 'useErrorDialogs': false, + 'stickyAuth': false, + 'sensitiveTransaction': false, + }..addAll(const AndroidAuthMessages().args)), + ], + ); + }); }); - test('authenticate with no sensitive transaction.', () async { - setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); - await localAuthentication.authenticateWithBiometrics( - localizedReason: 'Insecure', - sensitiveTransaction: false, - useErrorDialogs: false, - ); - expect( - log, - [ - isMethodCall('authenticateWithBiometrics', - arguments: { - 'localizedReason': 'Insecure', - 'useErrorDialogs': false, - 'stickyAuth': false, - 'sensitiveTransaction': false, - }..addAll(const AndroidAuthMessages().args)), - ], - ); + group("With biometrics only", () { + test('authenticate with no args on Android.', () async { + setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); + await localAuthentication.authenticate(localizedReason: 'Needs secure'); + expect( + log, + [ + isMethodCall('authenticate', + arguments: { + 'localizedReason': 'Needs secure', + 'useErrorDialogs': true, + 'stickyAuth': false, + 'sensitiveTransaction': true, + }..addAll(const AndroidAuthMessages().args)), + ], + ); + }); + + test('authenticate with no args on iOS.', () async { + setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); + await localAuthentication.authenticate(localizedReason: 'Needs secure'); + expect( + log, + [ + isMethodCall('authenticate', + arguments: { + 'localizedReason': 'Needs secure', + 'useErrorDialogs': true, + 'stickyAuth': false, + 'sensitiveTransaction': true, + }..addAll(const IOSAuthMessages().args)), + ], + ); + }); + + test('authenticate with no sensitive transaction.', () async { + setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); + await localAuthentication.authenticate( + localizedReason: 'Insecure', + sensitiveTransaction: false, + useErrorDialogs: false, + ); + expect( + log, + [ + isMethodCall('authenticate', + arguments: { + 'localizedReason': 'Insecure', + 'useErrorDialogs': false, + 'stickyAuth': false, + 'sensitiveTransaction': false, + }..addAll(const AndroidAuthMessages().args)), + ], + ); + }); }); }); } From 9bb138f62aaf4f5fe41ab1e472381e3c00084fa0 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 11:01:09 -0600 Subject: [PATCH 08/65] Add documentation comments --- .../plugins/localauth/LocalAuthPlugin.java | 6 +++ packages/local_auth/lib/local_auth.dart | 37 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 503909364c8d..69fe05047a5d 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -84,6 +84,9 @@ public void onMethodCall(MethodCall call, final Result result) { } } + /* + Starts authentication process + */ private void authenticate(MethodCall call, final Result result) { if (authInProgress.get()) { // Apps should not invoke another authentication request while one is in progress, @@ -156,6 +159,9 @@ private void stopAuthentication(final Result result) { } } + /* + Returns biometric types available on device + */ private void getAvailableBiometrics(final Result result) { try { if (activity == null || activity.isFinishing()) { diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index 5eaa9a124b78..348c8ea0790f 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -95,6 +95,43 @@ class LocalAuthentication { return await _channel.invokeMethod('authenticateWithBiometrics', args); } + /// Authenticates the user with biometrics available on the device while also + /// allowing the user to use device authentication - pin, pattern, passcode. + /// + /// Returns a [Future] holding true, if the user successfully authenticated, + /// false otherwise. + /// + /// [localizedReason] is the message to show to user while prompting them + /// for authentication. This is typically along the lines of: 'Please scan + /// your finger to access MyApp.' + /// + /// [useErrorDialogs] = true means the system will attempt to handle user + /// fixable issues encountered while authenticating. For instance, if + /// fingerprint reader exists on the phone but there's no fingerprint + /// registered, the plugin will attempt to take the user to settings to add + /// one. Anything that is not user fixable, such as no biometric sensor on + /// device, will be returned as a [PlatformException]. + /// + /// [stickyAuth] is used when the application goes into background for any + /// reason while the authentication is in progress. Due to security reasons, + /// the authentication has to be stopped at that time. If stickyAuth is set + /// to true, authentication resumes when the app is resumed. If it is set to + /// false (default), then as soon as app is paused a failure message is sent + /// back to Dart and it is up to the client app to restart authentication or + /// do something else. + /// + /// Construct [AndroidAuthStrings] and [IOSAuthStrings] if you want to + /// customize messages in the dialogs. + /// + /// Setting [sensitiveTransaction] to true enables platform specific + /// precautions. For instance, on face unlock, Android opens a confirmation + /// dialog after the face is recognized to make sure the user meant to unlock + /// their phone. + /// + /// Throws an [PlatformException] if there were technical problems with local + /// authentication (e.g. lack of relevant hardware). This might throw + /// [PlatformException] with error code [otherOperatingSystem] on the iOS + /// simulator. Future authenticate({ @required String localizedReason, bool useErrorDialogs = true, From 73240b08cae8fb6a17ead7bc3c5543d25fd4ef02 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 11:13:39 -0600 Subject: [PATCH 09/65] Increment version to 0.6.2 indicating a new feature --- packages/local_auth/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/pubspec.yaml b/packages/local_auth/pubspec.yaml index d73c0295d554..311dabf5ab2b 100644 --- a/packages/local_auth/pubspec.yaml +++ b/packages/local_auth/pubspec.yaml @@ -2,7 +2,7 @@ name: local_auth description: Flutter plugin for Android and iOS device authentication sensors such as Fingerprint Reader and Touch ID. homepage: https://github.com/flutter/plugins/tree/master/packages/local_auth -version: 0.6.1+2 +version: 0.6.2 flutter: plugin: From 2bd7267dc7827046e5865d9cbc96650d94c05c74 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 11:16:45 -0600 Subject: [PATCH 10/65] Add 0.6.2 changes to changelog --- packages/local_auth/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/local_auth/CHANGELOG.md b/packages/local_auth/CHANGELOG.md index 5812a533f68b..e6b547d10f09 100644 --- a/packages/local_auth/CHANGELOG.md +++ b/packages/local_auth/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.2 + +* Add `authenticate` method that uses biometric authentication, but allows users to also use device authentication - pin, pattern, passcode. + ## 0.6.1+2 * Support v2 embedding. From f54da0933f792595a075715870f5c187103c161d Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 11:43:44 -0600 Subject: [PATCH 11/65] Format code --- packages/local_auth/lib/local_auth.dart | 8 ++++++-- packages/local_auth/test/local_auth_e2e.dart | 5 ++++- packages/local_auth/test/local_auth_test.dart | 16 ++++++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index 348c8ea0790f..e59057361d2e 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -176,7 +176,9 @@ class LocalAuthentication { /// Returns true if device is capable of checking biometrics /// /// Returns a [Future] bool true or false: - Future get canCheckBiometrics async => (await _channel.invokeListMethod('getAvailableBiometrics')).isNotEmpty; + Future get canCheckBiometrics async => + (await _channel.invokeListMethod('getAvailableBiometrics')) + .isNotEmpty; /// Returns a list of enrolled biometrics /// @@ -185,7 +187,9 @@ class LocalAuthentication { /// - BiometricType.fingerprint /// - BiometricType.iris (not yet implemented) Future> getAvailableBiometrics() async { - final List result = (await _channel.invokeListMethod('getAvailableBiometrics')); + final List result = (await _channel.invokeListMethod( + 'getAvailableBiometrics', + )); final List biometrics = []; result.forEach((String value) { switch (value) { diff --git a/packages/local_auth/test/local_auth_e2e.dart b/packages/local_auth/test/local_auth_e2e.dart index 5e9dc57b73f6..da9dce6c8552 100644 --- a/packages/local_auth/test/local_auth_e2e.dart +++ b/packages/local_auth/test/local_auth_e2e.dart @@ -7,6 +7,9 @@ void main() { E2EWidgetsFlutterBinding.ensureInitialized(); testWidgets('canCheckBiometrics', (WidgetTester tester) async { - expect(LocalAuthentication().getAvailableBiometrics(), completion(isList)); + expect( + LocalAuthentication().getAvailableBiometrics(), + completion(isList), + ); }); } diff --git a/packages/local_auth/test/local_auth_test.dart b/packages/local_auth/test/local_auth_test.dart index 579c541d6f56..b331d65adc91 100644 --- a/packages/local_auth/test/local_auth_test.dart +++ b/packages/local_auth/test/local_auth_test.dart @@ -33,7 +33,9 @@ void main() { group("With device auth fail over", () { test('authenticate with no args on Android.', () async { setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); - await localAuthentication.authenticateWithBiometrics(localizedReason: 'Needs secure'); + await localAuthentication.authenticateWithBiometrics( + localizedReason: 'Needs secure', + ); expect( log, [ @@ -50,7 +52,9 @@ void main() { test('authenticate with no args on iOS.', () async { setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); - await localAuthentication.authenticateWithBiometrics(localizedReason: 'Needs secure'); + await localAuthentication.authenticateWithBiometrics( + localizedReason: 'Needs secure', + ); expect( log, [ @@ -90,7 +94,9 @@ void main() { group("With biometrics only", () { test('authenticate with no args on Android.', () async { setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); - await localAuthentication.authenticate(localizedReason: 'Needs secure'); + await localAuthentication.authenticate( + localizedReason: 'Needs secure', + ); expect( log, [ @@ -107,7 +113,9 @@ void main() { test('authenticate with no args on iOS.', () async { setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); - await localAuthentication.authenticate(localizedReason: 'Needs secure'); + await localAuthentication.authenticate( + localizedReason: 'Needs secure', + ); expect( log, [ From 34d730a406701b2b329e2542276e19fb3b0e3ebb Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 11:57:43 -0600 Subject: [PATCH 12/65] Format code --- packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m | 8 +++----- packages/local_auth/lib/local_auth.dart | 12 ++++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m index 5d3229ba7018..f0b65ad82a39 100644 --- a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m @@ -88,7 +88,6 @@ - (void)getAvailableBiometrics:(FlutterResult)result { } result(biometrics); } - - (void)authenticateWithBiometrics:(NSDictionary *)arguments withFlutterResult:(FlutterResult)result { LAContext *context = [[LAContext alloc] init]; @@ -129,16 +128,15 @@ - (void)authenticateWithBiometrics:(NSDictionary *)arguments } } -- (void)authenticate:(NSDictionary *)arguments - withFlutterResult:(FlutterResult)result { + +- (void)authenticate:(NSDictionary *)arguments withFlutterResult:(FlutterResult)result { LAContext *context = [[LAContext alloc] init]; NSError *authError = nil; lastCallArgs = nil; lastResult = nil; context.localizedFallbackTitle = @""; - if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication - error:&authError]) { + if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) { [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:arguments[@"localizedReason"] reply:^(BOOL success, NSError *error) { diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index e59057361d2e..e86a77cdad37 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -92,7 +92,10 @@ class LocalAuthentication { 'operating systems.', details: 'Your operating system is ${_platform.operatingSystem}'); } - return await _channel.invokeMethod('authenticateWithBiometrics', args); + return await _channel.invokeMethod( + 'authenticateWithBiometrics', + args, + ); } /// Authenticates the user with biometrics available on the device while also @@ -154,7 +157,8 @@ class LocalAuthentication { } else { throw PlatformException( code: otherOperatingSystem, - message: 'Local authentication does not support non-Android/iOS operating systems.', + message: 'Local authentication does not support non-Android/iOS ' + 'operating systems.', details: 'Your operating system is ${_platform.operatingSystem}', ); } @@ -176,9 +180,9 @@ class LocalAuthentication { /// Returns true if device is capable of checking biometrics /// /// Returns a [Future] bool true or false: - Future get canCheckBiometrics async => + Future get canCheckBiometrics async => (await _channel.invokeListMethod('getAvailableBiometrics')) - .isNotEmpty; + .isNotEmpty; /// Returns a list of enrolled biometrics /// From 850513770edd4170a046c406612bdbcb01159906 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 12:11:00 -0600 Subject: [PATCH 13/65] Format code --- .../localauth/AuthenticationHelper.java | 30 +++++++++---------- .../plugins/localauth/LocalAuthPlugin.java | 13 ++++---- packages/local_auth/example/lib/main.dart | 17 ++++++++--- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index 507c16dbbf65..5fd29695b87c 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -84,24 +84,24 @@ interface AuthCompletionHandler { this.isAuthSticky = call.argument("stickyAuth"); this.uiThreadExecutor = new UiThreadExecutor(); - if(failOverToDeviceAuth){ + if (failOverToDeviceAuth) { this.promptInfo = - new BiometricPrompt.PromptInfo.Builder() - .setDescription((String) call.argument("localizedReason")) - .setTitle((String) call.argument("signInTitle")) - .setSubtitle((String) call.argument("fingerprintHint")) - .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) - .setDeviceCredentialAllowed(true) - .build(); + new BiometricPrompt.PromptInfo.Builder() + .setDescription((String) call.argument("localizedReason")) + .setTitle((String) call.argument("signInTitle")) + .setSubtitle((String) call.argument("fingerprintHint")) + .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) + .setDeviceCredentialAllowed(true) + .build(); } else { this.promptInfo = - new BiometricPrompt.PromptInfo.Builder() - .setDescription((String) call.argument("localizedReason")) - .setTitle((String) call.argument("signInTitle")) - .setSubtitle((String) call.argument("fingerprintHint")) - .setNegativeButtonText((String) call.argument("cancelButton")) - .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) - .build(); + new BiometricPrompt.PromptInfo.Builder() + .setDescription((String) call.argument("localizedReason")) + .setTitle((String) call.argument("signInTitle")) + .setSubtitle((String) call.argument("fingerprintHint")) + .setNegativeButtonText((String) call.argument("cancelButton")) + .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) + .build(); } } diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 69fe05047a5d..b2e2c6af4fa4 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -46,8 +46,8 @@ public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, Activi * initialized this way won't react to changes in activity or context. * * @param registrar attaches this plugin's {@link - * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to the registrar's {@link - * io.flutter.plugin.common.BinaryMessenger}. + * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to the registrar's {@link + * io.flutter.plugin.common.BinaryMessenger}. */ public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); @@ -104,15 +104,16 @@ private void authenticate(MethodCall call, final Result result) { if (!(activity instanceof FragmentActivity)) { result.error( - "no_fragment_activity", - "local_auth plugin requires activity to be a FragmentActivity.", - null); + "no_fragment_activity", + "local_auth plugin requires activity to be a FragmentActivity.", + null); return; } authInProgress.set(true); boolean failoverToDeviceAuth = call.method.equals("authenticate"); - authenticationHelper = new AuthenticationHelper( + authenticationHelper = + new AuthenticationHelper( lifecycle, (FragmentActivity) activity, call, diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index ba74ab337116..b98935767a4d 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -61,7 +61,10 @@ class _MyAppState extends State { _isAuthenticating = true; _authorized = 'Authenticating'; }); - authenticated = await auth.authenticate(localizedReason: 'Let OS determine authentication method', useErrorDialogs: true, stickyAuth: true); + authenticated = await auth.authenticate( + localizedReason: 'Let OS determine authentication method', + useErrorDialogs: true, + stickyAuth: true); setState(() { _isAuthenticating = false; _authorized = 'Authenticating'; @@ -71,7 +74,8 @@ class _MyAppState extends State { } if (!mounted) return; - setState(() => _authorized = authenticated ? 'Authorized' : 'Not Authorized'); + setState( + () => _authorized = authenticated ? 'Authorized' : 'Not Authorized'); } Future _authenticateWithBiometrics() async { @@ -81,7 +85,10 @@ class _MyAppState extends State { _isAuthenticating = true; _authorized = 'Authenticating'; }); - authenticated = await auth.authenticateWithBiometrics(localizedReason: 'Scan your fingerprint to authenticate', useErrorDialogs: true, stickyAuth: true); + authenticated = await auth.authenticateWithBiometrics( + localizedReason: 'Scan your fingerprint to authenticate', + useErrorDialogs: true, + stickyAuth: true); setState(() { _isAuthenticating = false; _authorized = 'Authenticating'; @@ -146,7 +153,9 @@ class _MyAppState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text(_isAuthenticating ? 'Cancel' : 'Authenticate: biometrics only'), + Text(_isAuthenticating + ? 'Cancel' + : 'Authenticate: biometrics only'), Icon(Icons.fingerprint), ], ), From 968018bafdaf55c8e8c2a6d8067a3b758821a7ca Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 13:15:40 -0600 Subject: [PATCH 14/65] Format code --- packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m index f0b65ad82a39..92c5bc84a2a0 100644 --- a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m @@ -128,7 +128,6 @@ - (void)authenticateWithBiometrics:(NSDictionary *)arguments } } - - (void)authenticate:(NSDictionary *)arguments withFlutterResult:(FlutterResult)result { LAContext *context = [[LAContext alloc] init]; NSError *authError = nil; From e7337e1ff9185dcbedf8ef820d8172ce2e8c1768 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 13:41:16 -0600 Subject: [PATCH 15/65] Update readme example --- packages/local_auth/README.md | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/local_auth/README.md b/packages/local_auth/README.md index ca2aa49bed23..a6fbce276523 100644 --- a/packages/local_auth/README.md +++ b/packages/local_auth/README.md @@ -23,8 +23,8 @@ bool canCheckBiometrics = Currently the following biometric types are implemented: -* BiometricType.face -* BiometricType.fingerprint +- BiometricType.face +- BiometricType.fingerprint To get a list of enrolled biometrics, call getAvailableBiometrics: @@ -44,10 +44,10 @@ if (Platform.isIOS) { We have default dialogs with an 'OK' button to show authentication error messages for the following 2 cases: -1. Passcode/PIN/Pattern Not Set. The user has not yet configured a passcode on - iOS or PIN/pattern on Android. -2. Touch ID/Fingerprint Not Enrolled. The user has not enrolled any - fingerprints on the device. +1. Passcode/PIN/Pattern Not Set. The user has not yet configured a passcode on + iOS or PIN/pattern on Android. +2. Touch ID/Fingerprint Not Enrolled. The user has not enrolled any + fingerprints on the device. Which means, if there's no fingerprint on the user's device, a dialog with instructions will pop up to let the user set up fingerprint. If the user clicks @@ -55,13 +55,28 @@ instructions will pop up to let the user set up fingerprint. If the user clicks Use the exported APIs to trigger local authentication with default dialogs: +The `authenticate()` method uses biometric authentication, but also allows +users to use pin, pattern, or passcode. + +```dart +var localAuth = LocalAuthentication(); +bool didAuthenticate = + await localAuth.authenticate( + localizedReason: 'Please authenticate to show account balance'); +``` + +The `authenticateWithBiometrics()` method uses biometric authentication only. + ```dart var localAuth = LocalAuthentication(); bool didAuthenticate = await localAuth.authenticateWithBiometrics( - localizedReason: 'Please authenticate to show account balance'); + localizedReason: 'Please authenticate to show account balance'); ``` +Note that `authenticate()` and `authenticateWithBiometrics()` methods have +the same signature and parameters. + If you don't want to use the default dialogs, call this API with 'useErrorDialogs = false'. In this case, it will throw the error message back and you need to handle them in your dart code: @@ -134,7 +149,6 @@ you need to also add: to your Info.plist file. Failure to do so results in a dialog that tells the user your app has not been updated to use TouchID. - ## Android Integration Note that local_auth plugin requires the use of a FragmentActivity as @@ -155,7 +169,7 @@ Update your project's `AndroidManifest.xml` file to include the On Android, you can check only for existence of fingerprint hardware prior to API 29 (Android Q). Therefore, if you would like to support other biometrics types (such as face scanning) and you want to support SDKs lower than Q, -*do not* call `getAvailableBiometrics`. Simply call `authenticateWithBiometrics`. +_do not_ call `getAvailableBiometrics`. Simply call `authenticateWithBiometrics`. This will return an error if there was no hardware available. ## Sticky Auth From 972d5da99735a0d6d2a15a99731aea7a67b81ddf Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 16:25:34 -0600 Subject: [PATCH 16/65] Update gradle version --- packages/local_auth/example/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/example/android/build.gradle b/packages/local_auth/example/android/build.gradle index 541636cc492a..6de372893df4 100644 --- a/packages/local_auth/example/android/build.gradle +++ b/packages/local_auth/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.0' + classpath 'com.android.tools.build:gradle:3.5.3' } } From ee8b92ce494e7f189491355edde18df1188e9a08 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 30 Jan 2020 16:40:43 -0600 Subject: [PATCH 17/65] Update gradle version --- .../example/android/gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties index 562393332f6c..088b283c7059 100644 --- a/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu May 30 07:21:52 NPT 2019 +#Thu Jan 30 16:38:47 CST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip From 03ab18ce2eb954178adc7d2326eaa72d6352f47d Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 12 Mar 2020 18:26:52 -0500 Subject: [PATCH 18/65] Handle older versions of the SDK 23+ Add isDeviceSupported method for others --- .../android/src/main/AndroidManifest.xml | 2 + .../localauth/AuthenticationHelper.java | 38 ++- .../plugins/localauth/LocalAuthPlugin.java | 290 ++++++++++++------ .../local_auth/example/android/build.gradle | 2 +- .../example/android/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- 6 files changed, 237 insertions(+), 101 deletions(-) diff --git a/packages/local_auth/android/src/main/AndroidManifest.xml b/packages/local_auth/android/src/main/AndroidManifest.xml index b7da0caab6da..cb6cb985a986 100644 --- a/packages/local_auth/android/src/main/AndroidManifest.xml +++ b/packages/local_auth/android/src/main/AndroidManifest.xml @@ -1,3 +1,5 @@ + + diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index 5fd29695b87c..8554f2ab2d2e 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -7,10 +7,12 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.Application; +import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -20,6 +22,7 @@ import android.view.View; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.biometric.BiometricPrompt; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.DefaultLifecycleObserver; @@ -28,6 +31,8 @@ import io.flutter.plugin.common.MethodCall; import java.util.concurrent.Executor; +import static android.content.Context.KEYGUARD_SERVICE; + /** * Authenticates the user with fingerprint and sends corresponding response back to Flutter. * @@ -76,7 +81,7 @@ interface AuthCompletionHandler { FragmentActivity activity, MethodCall call, AuthCompletionHandler completionHandler, - boolean failOverToDeviceAuth) { + boolean allowCredentials) { this.lifecycle = lifecycle; this.activity = activity; this.completionHandler = completionHandler; @@ -84,7 +89,7 @@ interface AuthCompletionHandler { this.isAuthSticky = call.argument("stickyAuth"); this.uiThreadExecutor = new UiThreadExecutor(); - if (failOverToDeviceAuth) { + if (allowCredentials) { this.promptInfo = new BiometricPrompt.PromptInfo.Builder() .setDescription((String) call.argument("localizedReason")) @@ -93,6 +98,7 @@ interface AuthCompletionHandler { .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) .setDeviceCredentialAllowed(true) .build(); + } else { this.promptInfo = new BiometricPrompt.PromptInfo.Builder() @@ -138,21 +144,25 @@ private void stop() { public void onAuthenticationError(int errorCode, CharSequence errString) { switch (errorCode) { case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL: - completionHandler.onError( - "PasscodeNotSet", - "Phone not secured by PIN, pattern or password, or SIM is currently locked."); - break; + if (call.argument("useErrorDialogs")) { + showGoToSettingsDialog( + "Device credentials required", + "Device credentials security does not appear to be set up. Go to \'Settings > Security\' to add credentials."); + return; + } + completionHandler.onError("NotAvailable", "Security credentials not available."); case BiometricPrompt.ERROR_NO_SPACE: case BiometricPrompt.ERROR_NO_BIOMETRICS: + if(promptInfo.isDeviceCredentialAllowed())return; if (call.argument("useErrorDialogs")) { - showGoToSettingsDialog(); + showGoToSettingsDialog((String) call.argument("fingerprintRequired"), (String) call.argument("goToSettingDescription")); return; } completionHandler.onError("NotEnrolled", "No Biometrics enrolled on this device."); break; case BiometricPrompt.ERROR_HW_UNAVAILABLE: case BiometricPrompt.ERROR_HW_NOT_PRESENT: - completionHandler.onError("NotAvailable", "Biometrics is not available on this device."); + completionHandler.onError("NotAvailable", "Security credentials not available."); break; case BiometricPrompt.ERROR_LOCKOUT: completionHandler.onError( @@ -226,19 +236,23 @@ public void onResume(@NonNull LifecycleOwner owner) { onActivityResumed(null); } + //hack: dialog opens twice on Android 8 + private boolean isSettingsDialogOpen = false; // Suppress inflateParams lint because dialogs do not need to attach to a parent view. @SuppressLint("InflateParams") - private void showGoToSettingsDialog() { + private void showGoToSettingsDialog(String title, String descriptionText) { + if(isSettingsDialogOpen) return; View view = LayoutInflater.from(activity).inflate(R.layout.go_to_setting, null, false); TextView message = (TextView) view.findViewById(R.id.fingerprint_required); TextView description = (TextView) view.findViewById(R.id.go_to_setting_description); - message.setText((String) call.argument("fingerprintRequired")); - description.setText((String) call.argument("goToSettingDescription")); + message.setText(title); + description.setText(descriptionText); Context context = new ContextThemeWrapper(activity, R.style.AlertDialogCustom); OnClickListener goToSettingHandler = new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { + isSettingsDialogOpen = false; completionHandler.onFailure(); stop(); activity.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS)); @@ -248,10 +262,12 @@ public void onClick(DialogInterface dialog, int which) { new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { + isSettingsDialogOpen = false; completionHandler.onFailure(); stop(); } }; + isSettingsDialogOpen = true; new AlertDialog.Builder(context) .setView(view) .setPositiveButton((String) call.argument("goToSetting"), goToSettingHandler) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index b2e2c6af4fa4..86c77938ef12 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -5,10 +5,16 @@ package io.flutter.plugins.localauth; import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; +import android.hardware.fingerprint.FingerprintManager; import android.os.Build; + import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.Lifecycle; + import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; @@ -17,56 +23,85 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugins.localauth.AuthenticationHelper.AuthCompletionHandler; + import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; +import static android.app.Activity.RESULT_OK; +import static android.content.Context.KEYGUARD_SERVICE; + /** * Flutter plugin providing access to local authentication. * - *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. + *

+ * Instantiate this in an add to app scenario to gracefully handle activity and + * context changes. */ @SuppressWarnings("deprecation") public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware { private static final String CHANNEL_NAME = "plugins.flutter.io/local_auth"; + private static final int LOCK_REQUEST_CODE = 221; + + private ActivityPluginBinding activityPluginBinding; + private Registrar registrar; + + private Activity getActivity() { + if (activityPluginBinding != null) { + return activityPluginBinding.getActivity(); + } + if (registrar != null) { + return registrar.activity(); + } + return null; + } - private Activity activity; private final AtomicBoolean authInProgress = new AtomicBoolean(false); - private AuthenticationHelper authenticationHelper; + private AuthenticationHelper authHelper; // These are null when not using v2 embedding. private MethodChannel channel; private Lifecycle lifecycle; + private Result result; /** - * Registers a plugin with the v1 embedding api {@code io.flutter.plugin.common}. + * Registers a plugin with the v1 embedding api + * {@code io.flutter.plugin.common}. * - *

Calling this will register the plugin with the passed registrar. However, plugins - * initialized this way won't react to changes in activity or context. + *

+ * Calling this will register the plugin with the passed registrar. However, + * plugins initialized this way won't react to changes in activity or context. * - * @param registrar attaches this plugin's {@link - * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to the registrar's {@link - * io.flutter.plugin.common.BinaryMessenger}. + * @param registrar attaches this plugin's + * {@link io.flutter.plugin.common.MethodChannel.MethodCallHandler} + * to the registrar's + * {@link io.flutter.plugin.common.BinaryMessenger}. */ public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); - channel.setMethodCallHandler(new LocalAuthPlugin(registrar.activity())); - } + channel.setMethodCallHandler(new LocalAuthPlugin(registrar)); - private LocalAuthPlugin(Activity activity) { - this.activity = activity; } /** * Default constructor for LocalAuthPlugin. * - *

Use this constructor when adding this plugin to an app with v2 embedding. + *

+ * Use this constructor when adding this plugin to an app with v2 embedding. */ - public LocalAuthPlugin() {} + public LocalAuthPlugin() { + } + + private LocalAuthPlugin(Registrar registrar) { + this.registrar = registrar; + this.registrar.addActivityResultListener(resultListener); + } @Override public void onMethodCall(MethodCall call, final Result result) { + this.result = result; switch (call.method) { case "authenticate": case "authenticateWithBiometrics": @@ -75,8 +110,11 @@ public void onMethodCall(MethodCall call, final Result result) { case "getAvailableBiometrics": this.getAvailableBiometrics(result); break; + case "isDeviceSupported": + this.isDeviceSupported(result); + break; case "stopAuthentication": - this.stopAuthentication(result); + this.stopAuthentication(); break; default: result.notImplemented(); @@ -85,122 +123,201 @@ public void onMethodCall(MethodCall call, final Result result) { } /* - Starts authentication process - */ + * Starts authentication process + */ private void authenticate(MethodCall call, final Result result) { if (authInProgress.get()) { - // Apps should not invoke another authentication request while one is in progress, - // so we classify this as an error condition. If we ever find a legitimate use case for - // this, we can try to cancel the ongoing auth and start a new one but for now, not worth - // the complexity. result.error("auth_in_progress", "Authentication in progress", null); return; } + Activity activity = getActivity(); + if (activity == null || activity.isFinishing()) { result.error("no_activity", "local_auth plugin requires a foreground activity", null); return; } if (!(activity instanceof FragmentActivity)) { - result.error( - "no_fragment_activity", - "local_auth plugin requires activity to be a FragmentActivity.", - null); + result.error("no_fragment_activity", "local_auth plugin requires activity to be a FragmentActivity.", null); + return; + } + + if (!isDeviceSupported()) { + authInProgress.set(false); + result.error("NotAvailable", "Device not supported", null); return; } authInProgress.set(true); - boolean failoverToDeviceAuth = call.method.equals("authenticate"); - authenticationHelper = - new AuthenticationHelper( - lifecycle, - (FragmentActivity) activity, - call, - new AuthCompletionHandler() { - @Override - public void onSuccess() { - if (authInProgress.compareAndSet(true, false)) { - result.success(true); - } - } - - @Override - public void onFailure() { - if (authInProgress.compareAndSet(true, false)) { - result.success(false); - } - } - - @Override - public void onError(String code, String error) { - if (authInProgress.compareAndSet(true, false)) { - result.error(code, error, null); - } - } - }, - failoverToDeviceAuth); - authenticationHelper.authenticate(); + AuthCompletionHandler completionHandler = new AuthCompletionHandler() { + @Override + public void onSuccess() { + authenticateSuccess(); + } + + @Override + public void onFailure() { + authenticateFail(); + } + + @Override + public void onError(String code, String error) { + if (authInProgress.compareAndSet(true, false)) { + result.error(code, error, null); + } + } + }; + + // let authenticateWithBiometrics try biometric prompt - might not work + if (call.method.equals("authenticateWithBiometrics")) { + authHelper = new AuthenticationHelper(lifecycle, (FragmentActivity) activity, call, completionHandler, false); + authHelper.authenticate(); + return; + } + + // API 29 and above + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + authHelper = new AuthenticationHelper(lifecycle, (FragmentActivity) activity, call, completionHandler, true); + authHelper.authenticate(); + return; + } + + Context context = activity.getApplicationContext(); + + // API 23 - 28 with fingerprint + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + FingerprintManager fingerprintManager = (FingerprintManager) context + .getSystemService(Context.FINGERPRINT_SERVICE); + if (fingerprintManager.hasEnrolledFingerprints()) { + authHelper = new AuthenticationHelper(lifecycle, (FragmentActivity) activity, call, completionHandler, + false); + authHelper.authenticate(); + return; + } + } + + // API 23 or higher with device credentials + KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure()) { + String title = call.argument("signInTitle"); + String reason = call.argument("localizedReason"); + Intent authIntent = keyguardManager.createConfirmDeviceCredentialIntent(title, reason); + activity.startActivityForResult(authIntent, LOCK_REQUEST_CODE); + return; + } + + // Unable to authenticate + result.error("NotSupported", "This device does not support required security features", null); + } + + private void authenticateSuccess() { + if (authInProgress.compareAndSet(true, false)) { + result.success(true); + } + } + + private void authenticateFail() { + if (authInProgress.compareAndSet(true, false)) { + result.success(false); + } } /* - Stops the authentication if in progress. - */ - private void stopAuthentication(final Result result) { + * Stops the authentication if in progress. + */ + private void stopAuthentication() { try { - if (authenticationHelper != null && authInProgress.get()) { - authenticationHelper.stopAuthentication(); - authenticationHelper = null; - result.success(true); + if (authHelper != null && authInProgress.get()) { + authHelper.stopAuthentication(); + authHelper = null; return; } - result.success(false); + authInProgress.set(false); + result.success(true); } catch (Exception e) { result.success(false); } } /* - Returns biometric types available on device - */ + * Returns biometric types available on device + */ private void getAvailableBiometrics(final Result result) { try { + Activity activity = getActivity(); if (activity == null || activity.isFinishing()) { result.error("no_activity", "local_auth plugin requires a foreground activity", null); return; } - ArrayList biometrics = new ArrayList(); - PackageManager packageManager = activity.getPackageManager(); - if (Build.VERSION.SDK_INT >= 23) { - if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { - biometrics.add("fingerprint"); - } - } - if (Build.VERSION.SDK_INT >= 29) { - if (packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) { - biometrics.add("face"); - } - if (packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)) { - biometrics.add("iris"); - } - } + ArrayList biometrics = getAvailableBiometrics(); result.success(biometrics); } catch (Exception e) { result.error("no_biometrics_available", e.getMessage(), null); } } + private ArrayList getAvailableBiometrics() { + ArrayList biometrics = new ArrayList(); + Activity activity = getActivity(); + if (activity == null || activity.isFinishing()) { + return biometrics; + } + + PackageManager packageManager = activity.getPackageManager(); + if (Build.VERSION.SDK_INT >= 23) { + if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + biometrics.add("fingerprint"); + } + } + if (Build.VERSION.SDK_INT >= 29) { + if (packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) { + biometrics.add("face"); + } + if (packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)) { + biometrics.add("iris"); + } + } + + return biometrics; + } + + private boolean isDeviceSupported() { + KeyguardManager keyguardManager = (KeyguardManager) getActivity().getBaseContext().getSystemService(KEYGUARD_SERVICE); + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure()); + } + + private void isDeviceSupported(Result result) { + result.success(isDeviceSupported()); + } + @Override public void onAttachedToEngine(FlutterPluginBinding binding) { channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), CHANNEL_NAME); } @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) {} + public void onDetachedFromEngine(FlutterPluginBinding binding) { + } + + final PluginRegistry.ActivityResultListener resultListener = new PluginRegistry.ActivityResultListener() { + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == LOCK_REQUEST_CODE) { + if (resultCode == RESULT_OK) { + authenticateSuccess(); + } else { + authenticateFail(); + } + } + return false; + } + }; @Override public void onAttachedToActivity(ActivityPluginBinding binding) { - activity = binding.getActivity(); + activityPluginBinding = binding; + binding.addActivityResultListener(resultListener); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); channel.setMethodCallHandler(this); } @@ -208,18 +325,19 @@ public void onAttachedToActivity(ActivityPluginBinding binding) { @Override public void onDetachedFromActivityForConfigChanges() { lifecycle = null; - activity = null; + activityPluginBinding = null; } @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { - activity = binding.getActivity(); + activityPluginBinding = binding; + binding.addActivityResultListener(resultListener); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); } @Override public void onDetachedFromActivity() { - activity = null; + activityPluginBinding = null; lifecycle = null; channel.setMethodCallHandler(null); } diff --git a/packages/local_auth/example/android/build.gradle b/packages/local_auth/example/android/build.gradle index 6de372893df4..a37df8c7aad9 100644 --- a/packages/local_auth/example/android/build.gradle +++ b/packages/local_auth/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:3.6.0' } } diff --git a/packages/local_auth/example/android/gradle.properties b/packages/local_auth/example/android/gradle.properties index a6738207fd15..7fe61a74cee0 100644 --- a/packages/local_auth/example/android/gradle.properties +++ b/packages/local_auth/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx1024m android.useAndroidX=true android.enableJetifier=true android.enableR8=true diff --git a/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties index 088b283c7059..96602d2aa859 100644 --- a/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jan 30 16:38:47 CST 2020 +#Thu Mar 12 18:20:06 CDT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip From 8c8f35f56d5f9482bf3bebf3bf86688876558cce Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 12 Mar 2020 18:32:41 -0500 Subject: [PATCH 19/65] Add isDeviceSupported method --- packages/local_auth/example/lib/main.dart | 11 +++++++++++ packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m | 2 ++ packages/local_auth/lib/local_auth.dart | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index b98935767a4d..6d0477367366 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -21,11 +21,20 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { final LocalAuthentication auth = LocalAuthentication(); + + bool _isSupported; bool _canCheckBiometrics; List _availableBiometrics; String _authorized = 'Not Authorized'; bool _isAuthenticating = false; + @override + void initState() { + super.initState(); + auth.isDeviceSupported().then((isSupported) => setState(() => _isSupported = isSupported)); + } + + Future _checkBiometrics() async { bool canCheckBiometrics; try { @@ -122,6 +131,8 @@ class _MyAppState extends State { Column( mainAxisAlignment: MainAxisAlignment.center, children: [ + if (_isSupported == null) CircularProgressIndicator() else if (_isSupported) Text("This device is supported") else Text("This device is not supported"), + Divider(height: 100), Text('Can check biometrics: $_canCheckBiometrics\n'), RaisedButton( child: const Text('Check biometrics'), diff --git a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m index 92c5bc84a2a0..f42695be7eee 100644 --- a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m @@ -25,6 +25,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self authenticate:call.arguments withFlutterResult:result]; } else if ([@"getAvailableBiometrics" isEqualToString:call.method]) { [self getAvailableBiometrics:result]; + } else if ([@"isDeviceSupported" isEqualToString:call.method]) { + result(@YES); } else { result(FlutterMethodNotImplemented); } diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index e86a77cdad37..3275ded07f8d 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -184,6 +184,12 @@ class LocalAuthentication { (await _channel.invokeListMethod('getAvailableBiometrics')) .isNotEmpty; + /// Returns true if device is capable of checking biometrics or is able to + /// fail over to device credentials. + /// + /// Returns a [Future] bool true or false: + Future isDeviceSupported() => _channel.invokeMethod('isDeviceSupported'); + /// Returns a list of enrolled biometrics /// /// Returns a [Future] List with the following possibilities: From ea4820460e5470ad169c2c55c0176c884fcc7fe4 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 12 Mar 2020 21:05:22 -0500 Subject: [PATCH 20/65] Format code --- packages/local_auth/example/lib/main.dart | 14 ++++++++++---- packages/local_auth/lib/local_auth.dart | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index 6d0477367366..1ffb4957b090 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -21,7 +21,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { final LocalAuthentication auth = LocalAuthentication(); - + bool _isSupported; bool _canCheckBiometrics; List _availableBiometrics; @@ -31,10 +31,11 @@ class _MyAppState extends State { @override void initState() { super.initState(); - auth.isDeviceSupported().then((isSupported) => setState(() => _isSupported = isSupported)); + auth + .isDeviceSupported() + .then((isSupported) => setState(() => _isSupported = isSupported)); } - Future _checkBiometrics() async { bool canCheckBiometrics; try { @@ -131,7 +132,12 @@ class _MyAppState extends State { Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (_isSupported == null) CircularProgressIndicator() else if (_isSupported) Text("This device is supported") else Text("This device is not supported"), + if (_isSupported == null) + CircularProgressIndicator() + else if (_isSupported) + Text("This device is supported") + else + Text("This device is not supported"), Divider(height: 100), Text('Can check biometrics: $_canCheckBiometrics\n'), RaisedButton( diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index 3275ded07f8d..5e3db3ba9aaa 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -188,7 +188,8 @@ class LocalAuthentication { /// fail over to device credentials. /// /// Returns a [Future] bool true or false: - Future isDeviceSupported() => _channel.invokeMethod('isDeviceSupported'); + Future isDeviceSupported() => + _channel.invokeMethod('isDeviceSupported'); /// Returns a list of enrolled biometrics /// From b76ac35e89fc4ec9292a6e684a8ec61436c2861e Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Fri, 13 Mar 2020 08:34:09 -0500 Subject: [PATCH 21/65] Use ternary instead of Visibility widget --- packages/local_auth/example/lib/main.dart | 67 +++++++++++------------ 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index 1ffb4957b090..856d6a036ffc 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -152,48 +152,43 @@ class _MyAppState extends State { ), Divider(height: 100), Text('Current State: $_authorized\n'), - Visibility( - visible: !_isAuthenticating, - child: Column( - children: [ - RaisedButton( + (_isAuthenticating) + ? RaisedButton( + onPressed: _cancelAuthentication, child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text('Authenticate'), - Icon(Icons.perm_device_information), + Text("Cancel Authentication"), + Icon(Icons.cancel), ], ), - onPressed: _authenticate, + ) + : Column( + children: [ + RaisedButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Authenticate'), + Icon(Icons.perm_device_information), + ], + ), + onPressed: _authenticate, + ), + RaisedButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(_isAuthenticating + ? 'Cancel' + : 'Authenticate: biometrics only'), + Icon(Icons.fingerprint), + ], + ), + onPressed: _authenticateWithBiometrics, + ), + ], ), - RaisedButton( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(_isAuthenticating - ? 'Cancel' - : 'Authenticate: biometrics only'), - Icon(Icons.fingerprint), - ], - ), - onPressed: _authenticateWithBiometrics, - ), - ], - ), - ), - Visibility( - visible: _isAuthenticating, - child: RaisedButton( - onPressed: _cancelAuthentication, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text("Cancel Authentication"), - Icon(Icons.cancel), - ], - ), - ), - ), ], ), ], From 51945baa982dcc5c466d3b10b3e49a790f36d4c0 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 12:14:18 -0600 Subject: [PATCH 22/65] Clean up --- .../ios/Classes/FLTLocalAuthPlugin.m | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m index 64e129834405..8b62117db1c4 100644 --- a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m @@ -136,39 +136,43 @@ - (void)authenticateWithBiometrics:(NSDictionary *)arguments - (void)authenticate:(NSDictionary *)arguments withFlutterResult:(FlutterResult)result { LAContext *context = [[LAContext alloc] init]; NSError *authError = nil; - lastCallArgs = nil; - lastResult = nil; + _lastCallArgs = nil; + _lastResult = nil; context.localizedFallbackTitle = @""; - if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) { - [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication - localizedReason:arguments[@"localizedReason"] - reply:^(BOOL success, NSError *error) { - if (success) { - result(@YES); - } else { - switch (error.code) { - case LAErrorPasscodeNotSet: - case LAErrorTouchIDNotAvailable: - case LAErrorTouchIDNotEnrolled: - case LAErrorTouchIDLockout: - [self handleErrors:error - flutterArguments:arguments - withFlutterResult:result]; - return; - case LAErrorSystemCancel: - if ([arguments[@"stickyAuth"] boolValue]) { - lastCallArgs = arguments; - lastResult = result; + if (@available(iOS 9.0, *)) { + if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) { + [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication + localizedReason:arguments[@"localizedReason"] + reply:^(BOOL success, NSError *error) { + if (success) { + result(@YES); + } else { + switch (error.code) { + case LAErrorPasscodeNotSet: + case LAErrorTouchIDNotAvailable: + case LAErrorTouchIDNotEnrolled: + case LAErrorTouchIDLockout: + [self handleErrors:error + flutterArguments:arguments + withFlutterResult:result]; + return; + case LAErrorSystemCancel: + if ([arguments[@"stickyAuth"] boolValue]) { + self->_lastCallArgs = arguments; + self->_lastResult = result; return; - } - } - result(@NO); - } - }]; - } else { - [self handleErrors:authError flutterArguments:arguments withFlutterResult:result]; - } + } + } + result(@NO); + } + }]; + } else { + [self handleErrors:authError flutterArguments:arguments withFlutterResult:result]; + } + } else { + // Fallback on earlier versions + } } - (void)handleErrors:(NSError *)authError From 64c12d6cfb297492560a15f998a0f9f53f6b43af Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 12:28:22 -0600 Subject: [PATCH 23/65] Remove spaces --- packages/local_auth/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/local_auth/README.md b/packages/local_auth/README.md index cfeef52824ba..ff0401ec4cc8 100644 --- a/packages/local_auth/README.md +++ b/packages/local_auth/README.md @@ -62,7 +62,7 @@ users to use pin, pattern, or passcode. var localAuth = LocalAuthentication(); bool didAuthenticate = await localAuth.authenticate( - localizedReason: 'Please authenticate to show account balance'); + localizedReason: 'Please authenticate to show account balance'); ``` The `authenticateWithBiometrics()` method uses biometric authentication only. @@ -71,7 +71,7 @@ The `authenticateWithBiometrics()` method uses biometric authentication only. var localAuth = LocalAuthentication(); bool didAuthenticate = await localAuth.authenticateWithBiometrics( - localizedReason: 'Please authenticate to show account balance'); + localizedReason: 'Please authenticate to show account balance'); ``` Note that `authenticate()` and `authenticateWithBiometrics()` methods have From 7c24fb431bd99d8e0faf7e4d9a353f5f76eaedfa Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 13:05:39 -0600 Subject: [PATCH 24/65] format code --- .../ios/Classes/FLTLocalAuthPlugin.m | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m index 8b62117db1c4..90da886e8781 100644 --- a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m @@ -140,39 +140,39 @@ - (void)authenticate:(NSDictionary *)arguments withFlutterResult:(FlutterResult) _lastResult = nil; context.localizedFallbackTitle = @""; - if (@available(iOS 9.0, *)) { - if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) { - [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication - localizedReason:arguments[@"localizedReason"] - reply:^(BOOL success, NSError *error) { - if (success) { - result(@YES); - } else { - switch (error.code) { - case LAErrorPasscodeNotSet: - case LAErrorTouchIDNotAvailable: - case LAErrorTouchIDNotEnrolled: - case LAErrorTouchIDLockout: - [self handleErrors:error - flutterArguments:arguments - withFlutterResult:result]; - return; - case LAErrorSystemCancel: - if ([arguments[@"stickyAuth"] boolValue]) { - self->_lastCallArgs = arguments; - self->_lastResult = result; + if (@available(iOS 9.0, *)) { + if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) { + [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication + localizedReason:arguments[@"localizedReason"] + reply:^(BOOL success, NSError *error) { + if (success) { + result(@YES); + } else { + switch (error.code) { + case LAErrorPasscodeNotSet: + case LAErrorTouchIDNotAvailable: + case LAErrorTouchIDNotEnrolled: + case LAErrorTouchIDLockout: + [self handleErrors:error + flutterArguments:arguments + withFlutterResult:result]; return; + case LAErrorSystemCancel: + if ([arguments[@"stickyAuth"] boolValue]) { + self->_lastCallArgs = arguments; + self->_lastResult = result; + return; + } } - } - result(@NO); - } - }]; - } else { - [self handleErrors:authError flutterArguments:arguments withFlutterResult:result]; - } + result(@NO); + } + }]; } else { - // Fallback on earlier versions + [self handleErrors:authError flutterArguments:arguments withFlutterResult:result]; } + } else { + // Fallback on earlier versions + } } - (void)handleErrors:(NSError *)authError From 3aea84d9509fa580da1a7e1a84431151c99330c7 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 13:34:17 -0600 Subject: [PATCH 25/65] Format code --- .../localauth/AuthenticationHelper.java | 12 +- .../plugins/localauth/LocalAuthPlugin.java | 127 +++++++++--------- 2 files changed, 68 insertions(+), 71 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index 8554f2ab2d2e..712da8901f06 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -7,12 +7,10 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.Application; -import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -22,7 +20,6 @@ import android.view.View; import android.widget.TextView; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import androidx.biometric.BiometricPrompt; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.DefaultLifecycleObserver; @@ -31,7 +28,6 @@ import io.flutter.plugin.common.MethodCall; import java.util.concurrent.Executor; -import static android.content.Context.KEYGUARD_SERVICE; /** * Authenticates the user with fingerprint and sends corresponding response back to Flutter. @@ -153,9 +149,11 @@ public void onAuthenticationError(int errorCode, CharSequence errString) { completionHandler.onError("NotAvailable", "Security credentials not available."); case BiometricPrompt.ERROR_NO_SPACE: case BiometricPrompt.ERROR_NO_BIOMETRICS: - if(promptInfo.isDeviceCredentialAllowed())return; + if (promptInfo.isDeviceCredentialAllowed()) return; if (call.argument("useErrorDialogs")) { - showGoToSettingsDialog((String) call.argument("fingerprintRequired"), (String) call.argument("goToSettingDescription")); + showGoToSettingsDialog( + (String) call.argument("fingerprintRequired"), + (String) call.argument("goToSettingDescription")); return; } completionHandler.onError("NotEnrolled", "No Biometrics enrolled on this device."); @@ -241,7 +239,7 @@ public void onResume(@NonNull LifecycleOwner owner) { // Suppress inflateParams lint because dialogs do not need to attach to a parent view. @SuppressLint("InflateParams") private void showGoToSettingsDialog(String title, String descriptionText) { - if(isSettingsDialogOpen) return; + if (isSettingsDialogOpen) return; View view = LayoutInflater.from(activity).inflate(R.layout.go_to_setting, null, false); TextView message = (TextView) view.findViewById(R.id.fingerprint_required); TextView description = (TextView) view.findViewById(R.id.go_to_setting_description); diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index e5c9e27cf907..bd45ffb4766b 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -4,6 +4,9 @@ package io.flutter.plugins.localauth; +import static android.app.Activity.RESULT_OK; +import static android.content.Context.KEYGUARD_SERVICE; + import android.app.Activity; import android.app.KeyguardManager; import android.content.Context; @@ -11,10 +14,8 @@ import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; - import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.Lifecycle; - import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; @@ -26,19 +27,13 @@ import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugins.localauth.AuthenticationHelper.AuthCompletionHandler; - import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; -import static android.app.Activity.RESULT_OK; -import static android.content.Context.KEYGUARD_SERVICE; - /** * Flutter plugin providing access to local authentication. * - *

- * Instantiate this in an add to app scenario to gracefully handle activity and - * context changes. + *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. */ @SuppressWarnings("deprecation") public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware { @@ -67,33 +62,27 @@ private Activity getActivity() { private Result result; /** - * Registers a plugin with the v1 embedding api - * {@code io.flutter.plugin.common}. + * Registers a plugin with the v1 embedding api {@code io.flutter.plugin.common}. * - *

- * Calling this will register the plugin with the passed registrar. However, - * plugins initialized this way won't react to changes in activity or context. + *

Calling this will register the plugin with the passed registrar. However, plugins + * initialized this way won't react to changes in activity or context. * - * @param registrar attaches this plugin's - * {@link io.flutter.plugin.common.MethodChannel.MethodCallHandler} - * to the registrar's - * {@link io.flutter.plugin.common.BinaryMessenger}. + * @param registrar attaches this plugin's {@link + * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to the registrar's {@link + * io.flutter.plugin.common.BinaryMessenger}. */ @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); channel.setMethodCallHandler(new LocalAuthPlugin(registrar)); - } /** * Default constructor for LocalAuthPlugin. * - *

- * Use this constructor when adding this plugin to an app with v2 embedding. + *

Use this constructor when adding this plugin to an app with v2 embedding. */ - public LocalAuthPlugin() { - } + public LocalAuthPlugin() {} private LocalAuthPlugin(Registrar registrar) { this.registrar = registrar; @@ -140,7 +129,10 @@ private void authenticate(MethodCall call, final Result result) { } if (!(activity instanceof FragmentActivity)) { - result.error("no_fragment_activity", "local_auth plugin requires activity to be a FragmentActivity.", null); + result.error( + "no_fragment_activity", + "local_auth plugin requires activity to be a FragmentActivity.", + null); return; } @@ -151,35 +143,40 @@ private void authenticate(MethodCall call, final Result result) { } authInProgress.set(true); - AuthCompletionHandler completionHandler = new AuthCompletionHandler() { - @Override - public void onSuccess() { - authenticateSuccess(); - } - - @Override - public void onFailure() { - authenticateFail(); - } - - @Override - public void onError(String code, String error) { - if (authInProgress.compareAndSet(true, false)) { - result.error(code, error, null); - } - } - }; + AuthCompletionHandler completionHandler = + new AuthCompletionHandler() { + @Override + public void onSuccess() { + authenticateSuccess(); + } + + @Override + public void onFailure() { + authenticateFail(); + } + + @Override + public void onError(String code, String error) { + if (authInProgress.compareAndSet(true, false)) { + result.error(code, error, null); + } + } + }; // let authenticateWithBiometrics try biometric prompt - might not work if (call.method.equals("authenticateWithBiometrics")) { - authHelper = new AuthenticationHelper(lifecycle, (FragmentActivity) activity, call, completionHandler, false); + authHelper = + new AuthenticationHelper( + lifecycle, (FragmentActivity) activity, call, completionHandler, false); authHelper.authenticate(); return; } // API 29 and above if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - authHelper = new AuthenticationHelper(lifecycle, (FragmentActivity) activity, call, completionHandler, true); + authHelper = + new AuthenticationHelper( + lifecycle, (FragmentActivity) activity, call, completionHandler, true); authHelper.authenticate(); return; } @@ -188,11 +185,12 @@ public void onError(String code, String error) { // API 23 - 28 with fingerprint if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - FingerprintManager fingerprintManager = (FingerprintManager) context - .getSystemService(Context.FINGERPRINT_SERVICE); + FingerprintManager fingerprintManager = + (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); if (fingerprintManager.hasEnrolledFingerprints()) { - authHelper = new AuthenticationHelper(lifecycle, (FragmentActivity) activity, call, completionHandler, - false); + authHelper = + new AuthenticationHelper( + lifecycle, (FragmentActivity) activity, call, completionHandler, false); authHelper.authenticate(); return; } @@ -284,7 +282,8 @@ private ArrayList getAvailableBiometrics() { } private boolean isDeviceSupported() { - KeyguardManager keyguardManager = (KeyguardManager) getActivity().getBaseContext().getSystemService(KEYGUARD_SERVICE); + KeyguardManager keyguardManager = + (KeyguardManager) getActivity().getBaseContext().getSystemService(KEYGUARD_SERVICE); return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure()); } @@ -298,22 +297,22 @@ public void onAttachedToEngine(FlutterPluginBinding binding) { } @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { - } - - final PluginRegistry.ActivityResultListener resultListener = new PluginRegistry.ActivityResultListener() { - @Override - public boolean onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == LOCK_REQUEST_CODE) { - if (resultCode == RESULT_OK) { - authenticateSuccess(); - } else { - authenticateFail(); + public void onDetachedFromEngine(FlutterPluginBinding binding) {} + + final PluginRegistry.ActivityResultListener resultListener = + new PluginRegistry.ActivityResultListener() { + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == LOCK_REQUEST_CODE) { + if (resultCode == RESULT_OK) { + authenticateSuccess(); + } else { + authenticateFail(); + } + } + return false; } - } - return false; - } - }; + }; @Override public void onAttachedToActivity(ActivityPluginBinding binding) { From 39bb801bb0cd26c0f0bbc7076206900c78b4952a Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 13:35:42 -0600 Subject: [PATCH 26/65] Format --- .../java/io/flutter/plugins/localauth/AuthenticationHelper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index 712da8901f06..993b8ee85519 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -28,7 +28,6 @@ import io.flutter.plugin.common.MethodCall; import java.util.concurrent.Executor; - /** * Authenticates the user with fingerprint and sends corresponding response back to Flutter. * From 25dadc05ba39f6b13735395669efebd7bf317ae6 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 17:42:20 -0600 Subject: [PATCH 27/65] Refactor variable names --- .../localauth/AuthenticationHelper.java | 20 +++---- .../plugins/localauth/LocalAuthPlugin.java | 26 +++++++-- packages/local_auth/example/lib/main.dart | 4 +- packages/local_auth/lib/auth_strings.dart | 55 ++++++++++--------- packages/local_auth/lib/local_auth.dart | 4 +- 5 files changed, 61 insertions(+), 48 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index 993b8ee85519..22c8181f6ba9 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -29,7 +29,7 @@ import java.util.concurrent.Executor; /** - * Authenticates the user with fingerprint and sends corresponding response back to Flutter. + * Authenticates the user with biometrics and sends corresponding response back to Flutter. * *

One instance per call is generated to ensure readable separation of executable paths across * method calls. @@ -37,10 +37,8 @@ @SuppressWarnings("deprecation") class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { - /** The callback that handles the result of this authentication process. */ interface AuthCompletionHandler { - /** Called when authentication was successful. */ void onSuccess(); @@ -89,7 +87,7 @@ interface AuthCompletionHandler { new BiometricPrompt.PromptInfo.Builder() .setDescription((String) call.argument("localizedReason")) .setTitle((String) call.argument("signInTitle")) - .setSubtitle((String) call.argument("fingerprintHint")) + .setSubtitle((String) call.argument("biometricHint")) .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) .setDeviceCredentialAllowed(true) .build(); @@ -99,14 +97,14 @@ interface AuthCompletionHandler { new BiometricPrompt.PromptInfo.Builder() .setDescription((String) call.argument("localizedReason")) .setTitle((String) call.argument("signInTitle")) - .setSubtitle((String) call.argument("fingerprintHint")) + .setSubtitle((String) call.argument("biometricHint")) .setNegativeButtonText((String) call.argument("cancelButton")) .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) .build(); } } - /** Start the fingerprint listener. */ + /** Start the biometric listener. */ void authenticate() { if (lifecycle != null) { lifecycle.addObserver(this); @@ -117,7 +115,7 @@ void authenticate() { biometricPrompt.authenticate(promptInfo); } - /** Cancels the fingerprint authentication. */ + /** Cancels the biometric authentication. */ void stopAuthentication() { if (biometricPrompt != null) { biometricPrompt.cancelAuthentication(); @@ -125,7 +123,7 @@ void stopAuthentication() { } } - /** Stops the fingerprint listener. */ + /** Stops the biometric listener. */ private void stop() { if (lifecycle != null) { lifecycle.removeObserver(this); @@ -151,7 +149,7 @@ public void onAuthenticationError(int errorCode, CharSequence errString) { if (promptInfo.isDeviceCredentialAllowed()) return; if (call.argument("useErrorDialogs")) { showGoToSettingsDialog( - (String) call.argument("fingerprintRequired"), + (String) call.argument("biometricRequired"), (String) call.argument("goToSettingDescription")); return; } @@ -196,7 +194,7 @@ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult resul public void onAuthenticationFailed() {} /** - * If the activity is paused, we keep track because fingerprint dialog simply returns "User + * If the activity is paused, we keep track because biometric dialog simply returns "User * cancelled" when the activity is paused. */ @Override @@ -233,7 +231,7 @@ public void onResume(@NonNull LifecycleOwner owner) { onActivityResumed(null); } - //hack: dialog opens twice on Android 8 + // hack: dialog opens twice on Android 8 private boolean isSettingsDialogOpen = false; // Suppress inflateParams lint because dialogs do not need to attach to a parent view. @SuppressLint("InflateParams") diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index bd45ffb4766b..aa4814efb9c4 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -14,6 +14,7 @@ import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; +import androidx.biometric.BiometricManager; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.Lifecycle; import io.flutter.embedding.engine.plugins.FlutterPlugin; @@ -72,7 +73,7 @@ private Activity getActivity() { * io.flutter.plugin.common.BinaryMessenger}. */ @SuppressWarnings("deprecation") - public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { + public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); channel.setMethodCallHandler(new LocalAuthPlugin(registrar)); } @@ -138,7 +139,7 @@ private void authenticate(MethodCall call, final Result result) { if (!isDeviceSupported()) { authInProgress.set(false); - result.error("NotAvailable", "Device not supported", null); + result.error("NotAvailable", "Required security features not enabled", null); return; } @@ -165,6 +166,13 @@ public void onError(String code, String error) { // let authenticateWithBiometrics try biometric prompt - might not work if (call.method.equals("authenticateWithBiometrics")) { + if (!canAuthenticateWithBiometrics()) { + if (!hasBiometricHardware()) { + completionHandler.onError("NoHardware", "No biometric hardware found"); + } + completionHandler.onError("NotEnrolled", "No biometrics enrolled on this device."); + return; + } authHelper = new AuthenticationHelper( lifecycle, (FragmentActivity) activity, call, completionHandler, false); @@ -230,7 +238,6 @@ private void stopAuthentication() { if (authHelper != null && authInProgress.get()) { authHelper.stopAuthentication(); authHelper = null; - return; } authInProgress.set(false); result.success(true); @@ -262,7 +269,6 @@ private ArrayList getAvailableBiometrics() { if (activity == null || activity.isFinishing()) { return biometrics; } - PackageManager packageManager = activity.getPackageManager(); if (Build.VERSION.SDK_INT >= 23) { if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { @@ -287,13 +293,23 @@ private boolean isDeviceSupported() { return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure()); } + private boolean canAuthenticateWithBiometrics() { + BiometricManager biometricManager = BiometricManager.from(getActivity()); + return biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS; + } + + private boolean hasBiometricHardware() { + BiometricManager biometricManager = BiometricManager.from(getActivity()); + return biometricManager.canAuthenticate() != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; + } + private void isDeviceSupported(Result result) { result.success(isDeviceSupported()); } @Override public void onAttachedToEngine(FlutterPluginBinding binding) { - channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME); + channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), CHANNEL_NAME); } @Override diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index dd0e8ef993a0..afdc94ba85dd 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -22,7 +22,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { final LocalAuthentication auth = LocalAuthentication(); - late bool _isSupported; + bool? _isSupported; bool? _canCheckBiometrics; List? _availableBiometrics; String _authorized = 'Not Authorized'; @@ -138,7 +138,7 @@ class _MyAppState extends State { children: [ if (_isSupported == null) CircularProgressIndicator() - else if (_isSupported) + else if (_isSupported == true) Text("This device is supported") else Text("This device is not supported"), diff --git a/packages/local_auth/lib/auth_strings.dart b/packages/local_auth/lib/auth_strings.dart index 3afc23827d98..560c2157daf7 100644 --- a/packages/local_auth/lib/auth_strings.dart +++ b/packages/local_auth/lib/auth_strings.dart @@ -15,35 +15,35 @@ import 'package:intl/intl.dart'; /// Provides default values for all messages. class AndroidAuthMessages { const AndroidAuthMessages({ - this.fingerprintHint, - this.fingerprintNotRecognized, - this.fingerprintSuccess, + this.biometricHint, + this.biometricNotRecognized, + this.biometricSuccess, this.cancelButton, this.signInTitle, - this.fingerprintRequiredTitle, + this.biometricRequiredTitle, this.goToSettingsButton, this.goToSettingsDescription, }); - final String? fingerprintHint; - final String? fingerprintNotRecognized; - final String? fingerprintSuccess; + final String? biometricHint; + final String? biometricNotRecognized; + final String? biometricSuccess; final String? cancelButton; final String? signInTitle; - final String? fingerprintRequiredTitle; + final String? biometricRequiredTitle; final String? goToSettingsButton; final String? goToSettingsDescription; Map get args { return { - 'fingerprintHint': fingerprintHint ?? androidFingerprintHint, - 'fingerprintNotRecognized': - fingerprintNotRecognized ?? androidFingerprintNotRecognized, - 'fingerprintSuccess': fingerprintSuccess ?? androidFingerprintSuccess, + 'biometricHint': biometricHint ?? androidBiometricHint, + 'biometricNotRecognized': + biometricNotRecognized ?? androidBiometricNotRecognized, + 'biometricSuccess': biometricSuccess ?? androidBiometricSuccess, 'cancelButton': cancelButton ?? androidCancelButton, 'signInTitle': signInTitle ?? androidSignInTitle, - 'fingerprintRequired': - fingerprintRequiredTitle ?? androidFingerprintRequiredTitle, + 'biometricRequired': + biometricRequiredTitle ?? androidBiometricRequiredTitle, 'goToSetting': goToSettingsButton ?? goToSettings, 'goToSettingDescription': goToSettingsDescription ?? androidGoToSettingsDescription, @@ -80,16 +80,17 @@ class IOSAuthMessages { // Strings for local_authentication plugin. Currently supports English. // Intl.message must be string literals. -String get androidFingerprintHint => Intl.message('Touch sensor', - desc: 'Hint message advising the user how to scan their fingerprint. It is ' +String get androidBiometricHint => Intl.message('Verify identity', + desc: + 'Hint message advising the user how to authenticate with biometrics. It is ' 'used on Android side. Maximum 60 characters.'); -String get androidFingerprintNotRecognized => - Intl.message('Fingerprint not recognized. Try again.', +String get androidBiometricNotRecognized => + Intl.message('Not recognized. Try again.', desc: 'Message to let the user know that authentication was failed. It ' 'is used on Android side. Maximum 60 characters.'); -String get androidFingerprintSuccess => Intl.message('Fingerprint recognized.', +String get androidBiometricSuccess => Intl.message('Success', desc: 'Message to let the user know that authentication was successful. It ' 'is used on Android side. Maximum 60 characters.'); @@ -97,15 +98,15 @@ String get androidCancelButton => Intl.message('Cancel', desc: 'Message showed on a button that the user can click to leave the ' 'current dialog. It is used on Android side. Maximum 30 characters.'); -String get androidSignInTitle => Intl.message('Fingerprint Authentication', +String get androidSignInTitle => Intl.message('Authentication required', desc: 'Message showed as a title in a dialog which indicates the user ' - 'that they need to scan fingerprint to continue. It is used on ' + 'that they need to scan biometric to continue. It is used on ' 'Android side. Maximum 60 characters.'); -String get androidFingerprintRequiredTitle { - return Intl.message('Fingerprint required', +String get androidBiometricRequiredTitle { + return Intl.message('Biometric required', desc: 'Message showed as a title in a dialog which indicates the user ' - 'fingerprint is not set up yet on their device. It is used on Android' + 'has not set up biometric authentication on their device. It is used on Android' ' side. Maximum 60 characters.'); } @@ -115,10 +116,10 @@ String get goToSettings => Intl.message('Go to settings', 'and iOS side. Maximum 30 characters.'); String get androidGoToSettingsDescription => Intl.message( - 'Fingerprint is not set up on your device. Go to ' - '\'Settings > Security\' to add your fingerprint.', + 'Biometric authentication is not set up on your device. Go to ' + '\'Settings > Security\' to add biometric authentication.', desc: 'Message advising the user to go to the settings and configure ' - 'fingerprint on their device. It shows in a dialog on Android side.'); + 'biometric on their device. It shows in a dialog on Android side.'); String get iOSLockOut => Intl.message( 'Biometric authentication is disabled. Please lock and unlock your screen to ' diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index 1f46191fc6d9..d28b36957206 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -172,9 +172,7 @@ class LocalAuthentication { /// Returns [Future] bool true or false: Future stopAuthentication() async { if (_platform.isAndroid) { - final bool? result = - await _channel.invokeMethod('stopAuthentication'); - return result!; + return await _channel.invokeMethod('stopAuthentication') ?? false; } return true; } From 51ae491979584ef68a08aded9cadce9c3e410a16 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 17:43:19 -0600 Subject: [PATCH 28/65] Update version --- packages/local_auth/CHANGELOG.md | 4 ++++ packages/local_auth/pubspec.yaml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/local_auth/CHANGELOG.md b/packages/local_auth/CHANGELOG.md index 931eedbeba00..7e1c3480ce88 100644 --- a/packages/local_auth/CHANGELOG.md +++ b/packages/local_auth/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0-nullsafety + +* Allow pin, passcode, and pattern authentication + ## 1.0.0-nullsafety.2 * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) diff --git a/packages/local_auth/pubspec.yaml b/packages/local_auth/pubspec.yaml index 444eec2efa53..e6f9a833beaa 100644 --- a/packages/local_auth/pubspec.yaml +++ b/packages/local_auth/pubspec.yaml @@ -1,8 +1,8 @@ name: local_auth -description: Flutter plugin for Android and iOS device authentication sensors - such as Fingerprint Reader and Touch ID. +description: Flutter plugin for Android and iOS devices to allow local + authentication via fingerprint, touch ID, face ID, passcode, pin, or pattern. homepage: https://github.com/flutter/plugins/tree/master/packages/local_auth -version: 1.0.0-nullsafety.2 +version: 1.1.0-nullsafety flutter: plugin: From 58c07248bab8390c4d56dee08f2f39b1edf7c3ce Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 17:43:39 -0600 Subject: [PATCH 29/65] Gradle updates --- packages/integration_test/example/android/settings_aar.gradle | 1 + packages/local_auth/example/android/build.gradle | 2 +- packages/local_auth/example/android/settings_aar.gradle | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 packages/integration_test/example/android/settings_aar.gradle create mode 100644 packages/local_auth/example/android/settings_aar.gradle diff --git a/packages/integration_test/example/android/settings_aar.gradle b/packages/integration_test/example/android/settings_aar.gradle new file mode 100644 index 000000000000..e7b4def49cb5 --- /dev/null +++ b/packages/integration_test/example/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/packages/local_auth/example/android/build.gradle b/packages/local_auth/example/android/build.gradle index a37df8c7aad9..ea78cdf2c29c 100644 --- a/packages/local_auth/example/android/build.gradle +++ b/packages/local_auth/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.0' + classpath 'com.android.tools.build:gradle:4.1.1' } } diff --git a/packages/local_auth/example/android/settings_aar.gradle b/packages/local_auth/example/android/settings_aar.gradle new file mode 100644 index 000000000000..e7b4def49cb5 --- /dev/null +++ b/packages/local_auth/example/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' From 208d9f47f00f971fa599aeef81267e2e3f6aede8 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 20:20:04 -0600 Subject: [PATCH 30/65] Refactor: remove redundant setters --- .../localauth/AuthenticationHelper.java | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index 22c8181f6ba9..50240b1fa47c 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -81,27 +81,21 @@ interface AuthCompletionHandler { this.call = call; this.isAuthSticky = call.argument("stickyAuth"); this.uiThreadExecutor = new UiThreadExecutor(); + + BiometricPrompt.PromptInfo.Builder promptBuilder = + new BiometricPrompt.PromptInfo.Builder() + .setDescription((String) call.argument("localizedReason")) + .setTitle((String) call.argument("signInTitle")) + .setSubtitle((String) call.argument("biometricHint")) + .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) + .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")); if (allowCredentials) { - this.promptInfo = - new BiometricPrompt.PromptInfo.Builder() - .setDescription((String) call.argument("localizedReason")) - .setTitle((String) call.argument("signInTitle")) - .setSubtitle((String) call.argument("biometricHint")) - .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) - .setDeviceCredentialAllowed(true) - .build(); - + promptBuilder.setDeviceCredentialAllowed(true); } else { - this.promptInfo = - new BiometricPrompt.PromptInfo.Builder() - .setDescription((String) call.argument("localizedReason")) - .setTitle((String) call.argument("signInTitle")) - .setSubtitle((String) call.argument("biometricHint")) - .setNegativeButtonText((String) call.argument("cancelButton")) - .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) - .build(); + promptBuilder.setNegativeButtonText((String) call.argument("cancelButton")); } + this.promptInfo = promptBuilder.build(); } /** Start the biometric listener. */ From 773a9d71c9890ffc545b4146cb451ed0ab761023 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 20:39:08 -0600 Subject: [PATCH 31/65] Allow dialog text to be set in dart --- .../localauth/AuthenticationHelper.java | 6 +-- packages/local_auth/lib/auth_strings.dart | 41 +++++++++++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index 50240b1fa47c..65e4887d195a 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -81,7 +81,7 @@ interface AuthCompletionHandler { this.call = call; this.isAuthSticky = call.argument("stickyAuth"); this.uiThreadExecutor = new UiThreadExecutor(); - + BiometricPrompt.PromptInfo.Builder promptBuilder = new BiometricPrompt.PromptInfo.Builder() .setDescription((String) call.argument("localizedReason")) @@ -133,8 +133,8 @@ public void onAuthenticationError(int errorCode, CharSequence errString) { case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL: if (call.argument("useErrorDialogs")) { showGoToSettingsDialog( - "Device credentials required", - "Device credentials security does not appear to be set up. Go to \'Settings > Security\' to add credentials."); + (String) call.argument("deviceCredentialsRequired"), + (String) call.argument("deviceCredentialsSetupDescription")); return; } completionHandler.onError("NotAvailable", "Security credentials not available."); diff --git a/packages/local_auth/lib/auth_strings.dart b/packages/local_auth/lib/auth_strings.dart index 560c2157daf7..855098b6aba4 100644 --- a/packages/local_auth/lib/auth_strings.dart +++ b/packages/local_auth/lib/auth_strings.dart @@ -17,22 +17,26 @@ class AndroidAuthMessages { const AndroidAuthMessages({ this.biometricHint, this.biometricNotRecognized, + this.biometricRequiredTitle, this.biometricSuccess, this.cancelButton, - this.signInTitle, - this.biometricRequiredTitle, + this.deviceCredentialsRequiredTitle, + this.deviceCredentialsSetupDescription, this.goToSettingsButton, this.goToSettingsDescription, + this.signInTitle, }); final String? biometricHint; final String? biometricNotRecognized; + final String? biometricRequiredTitle; final String? biometricSuccess; final String? cancelButton; - final String? signInTitle; - final String? biometricRequiredTitle; + final String? deviceCredentialsRequiredTitle; + final String? deviceCredentialsSetupDescription; final String? goToSettingsButton; final String? goToSettingsDescription; + final String? signInTitle; Map get args { return { @@ -40,13 +44,17 @@ class AndroidAuthMessages { 'biometricNotRecognized': biometricNotRecognized ?? androidBiometricNotRecognized, 'biometricSuccess': biometricSuccess ?? androidBiometricSuccess, - 'cancelButton': cancelButton ?? androidCancelButton, - 'signInTitle': signInTitle ?? androidSignInTitle, 'biometricRequired': biometricRequiredTitle ?? androidBiometricRequiredTitle, + 'cancelButton': cancelButton ?? androidCancelButton, + 'deviceCredentialsRequired': deviceCredentialsRequiredTitle ?? + androidDeviceCredentialsRequiredTitle, + 'deviceCredentialsSetupDescription': deviceCredentialsSetupDescription ?? + androidDeviceCredentialsSetupDescription, 'goToSetting': goToSettingsButton ?? goToSettings, 'goToSettingDescription': goToSettingsDescription ?? androidGoToSettingsDescription, + 'signInTitle': signInTitle ?? androidSignInTitle, }; } } @@ -103,12 +111,21 @@ String get androidSignInTitle => Intl.message('Authentication required', 'that they need to scan biometric to continue. It is used on ' 'Android side. Maximum 60 characters.'); -String get androidBiometricRequiredTitle { - return Intl.message('Biometric required', - desc: 'Message showed as a title in a dialog which indicates the user ' - 'has not set up biometric authentication on their device. It is used on Android' - ' side. Maximum 60 characters.'); -} +String get androidBiometricRequiredTitle => Intl.message('Biometric required', + desc: 'Message showed as a title in a dialog which indicates the user ' + 'has not set up biometric authentication on their device. It is used on Android' + ' side. Maximum 60 characters.'); + +String get androidDeviceCredentialsRequiredTitle => Intl.message( + 'Device credentials required', + desc: 'Message showed as a title in a dialog which indicates the user ' + 'has not set up credentials authentication on their device. It is used on Android' + ' side. Maximum 60 characters.'); + +String get androidDeviceCredentialsSetupDescription => Intl.message( + 'Device credentials required', + desc: 'Message advising the user to go to the settings and configure ' + 'device credentials on their device. It shows in a dialog on Android side.'); String get goToSettings => Intl.message('Go to settings', desc: 'Message showed on a button that the user can click to go to ' From bebd37ab8ab6a9a02d616b0ae1b25f97901563cc Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 20:50:57 -0600 Subject: [PATCH 32/65] Use an enum to avoid null state --- packages/local_auth/example/lib/main.dart | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index afdc94ba85dd..475061f80bea 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -22,7 +22,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { final LocalAuthentication auth = LocalAuthentication(); - bool? _isSupported; + _SupportState _supportState = _SupportState.unknown; bool? _canCheckBiometrics; List? _availableBiometrics; String _authorized = 'Not Authorized'; @@ -31,9 +31,11 @@ class _MyAppState extends State { @override void initState() { super.initState(); - auth - .isDeviceSupported() - .then((isSupported) => setState(() => _isSupported = isSupported)); + auth.isDeviceSupported().then( + (isSupported) => setState(() => _supportState = isSupported + ? _SupportState.supported + : _SupportState.unsupported), + ); } Future _checkBiometrics() async { @@ -136,9 +138,9 @@ class _MyAppState extends State { Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (_isSupported == null) + if (_supportState == _SupportState.unknown) CircularProgressIndicator() - else if (_isSupported == true) + else if (_supportState == _SupportState.supported) Text("This device is supported") else Text("This device is not supported"), @@ -201,3 +203,9 @@ class _MyAppState extends State { ); } } + +enum _SupportState { + unknown, + supported, + unsupported, +} From f376def7c1f62e34fc0d278217ef98e184e2d551 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 21:06:44 -0600 Subject: [PATCH 33/65] Add breaking change note to changelog --- packages/local_auth/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/local_auth/CHANGELOG.md b/packages/local_auth/CHANGELOG.md index 7e1c3480ce88..2e2e6032ec1b 100644 --- a/packages/local_auth/CHANGELOG.md +++ b/packages/local_auth/CHANGELOG.md @@ -1,6 +1,11 @@ ## 1.1.0-nullsafety * Allow pin, passcode, and pattern authentication +* **Breaking change**. Parameter names refactored to use the generic `biometric` prefix in place of `fingerprint` in the `AndroidAuthMessages` class + * `fingerprintHint` is now `biometricHint` + * `fingerprintNotRecognized`is now `biometricNotRecognized` + * `fingerprintSuccess`is now `biometricSuccess` + * `fingerprintRequiredTitle` is now `biometricRequiredTitle` ## 1.0.0-nullsafety.2 From 65508fa54940ca04fa7898a9ba992eac9e9ca906 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 21:27:00 -0600 Subject: [PATCH 34/65] Return `Future` instead of `Future` --- packages/local_auth/example/lib/main.dart | 16 +++++++--------- packages/local_auth/lib/local_auth.dart | 13 ++++++------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index 475061f80bea..f0dd402a3712 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -75,11 +75,10 @@ class _MyAppState extends State { _isAuthenticating = true; _authorized = 'Authenticating'; }); - authenticated = (await auth.authenticate( - localizedReason: 'Let OS determine authentication method', - useErrorDialogs: true, - stickyAuth: true)) ?? - false; + authenticated = await auth.authenticate( + localizedReason: 'Let OS determine authentication method', + useErrorDialogs: true, + stickyAuth: true); setState(() { _isAuthenticating = false; _authorized = 'Authenticating'; @@ -101,10 +100,9 @@ class _MyAppState extends State { _authorized = 'Authenticating'; }); authenticated = await auth.authenticateWithBiometrics( - localizedReason: 'Scan your fingerprint to authenticate', - useErrorDialogs: true, - stickyAuth: true) ?? - false; + localizedReason: 'Scan your fingerprint to authenticate', + useErrorDialogs: true, + stickyAuth: true); setState(() { _isAuthenticating = false; _authorized = 'Authenticating'; diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index d28b36957206..06818609ffbc 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -66,7 +66,7 @@ class LocalAuthentication { /// authentication (e.g. lack of relevant hardware). This might throw /// [PlatformException] with error code [otherOperatingSystem] on the iOS /// simulator. - Future authenticateWithBiometrics({ + Future authenticateWithBiometrics({ required String localizedReason, bool useErrorDialogs = true, bool stickyAuth = false, @@ -92,10 +92,9 @@ class LocalAuthentication { 'operating systems.', details: 'Your operating system is ${_platform.operatingSystem}'); } - return await _channel.invokeMethod( - 'authenticateWithBiometrics', - args, - ); + return (await _channel.invokeMethod( + 'authenticateWithBiometrics', args)) ?? + false; } /// Authenticates the user with biometrics available on the device while also @@ -135,7 +134,7 @@ class LocalAuthentication { /// authentication (e.g. lack of relevant hardware). This might throw /// [PlatformException] with error code [otherOperatingSystem] on the iOS /// simulator. - Future authenticate({ + Future authenticate({ required String localizedReason, bool useErrorDialogs = true, bool stickyAuth = false, @@ -162,7 +161,7 @@ class LocalAuthentication { details: 'Your operating system is ${_platform.operatingSystem}', ); } - return await _channel.invokeMethod('authenticate', args); + return (await _channel.invokeMethod('authenticate', args)) ?? false; } /// Returns true if auth was cancelled successfully. From c43771f939814b1a933f5138e32fb472b602bc5b Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sat, 2 Jan 2021 21:33:16 -0600 Subject: [PATCH 35/65] Move `resultListener` property to top of class --- .../plugins/localauth/LocalAuthPlugin.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index aa4814efb9c4..1b2e8126121a 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -62,6 +62,21 @@ private Activity getActivity() { private Lifecycle lifecycle; private Result result; + final PluginRegistry.ActivityResultListener resultListener = + new PluginRegistry.ActivityResultListener() { + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == LOCK_REQUEST_CODE) { + if (resultCode == RESULT_OK) { + authenticateSuccess(); + } else { + authenticateFail(); + } + } + return false; + } + }; + /** * Registers a plugin with the v1 embedding api {@code io.flutter.plugin.common}. * @@ -315,21 +330,6 @@ public void onAttachedToEngine(FlutterPluginBinding binding) { @Override public void onDetachedFromEngine(FlutterPluginBinding binding) {} - final PluginRegistry.ActivityResultListener resultListener = - new PluginRegistry.ActivityResultListener() { - @Override - public boolean onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == LOCK_REQUEST_CODE) { - if (resultCode == RESULT_OK) { - authenticateSuccess(); - } else { - authenticateFail(); - } - } - return false; - } - }; - @Override public void onAttachedToActivity(ActivityPluginBinding binding) { activityPluginBinding = binding; From be61dc1634aaa053ed06bf1ea5351bb3512c486e Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Sun, 3 Jan 2021 20:14:20 -0600 Subject: [PATCH 36/65] Remove unneeded constructor --- .../io/flutter/plugins/localauth/LocalAuthPlugin.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 1b2e8126121a..7e11d12ab449 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -90,7 +90,9 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) { @SuppressWarnings("deprecation") public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); - channel.setMethodCallHandler(new LocalAuthPlugin(registrar)); + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + registrar.addActivityResultListener(plugin.resultListener); + channel.setMethodCallHandler(plugin); } /** @@ -99,12 +101,7 @@ public static void registerWith(Registrar registrar) { *

Use this constructor when adding this plugin to an app with v2 embedding. */ public LocalAuthPlugin() {} - - private LocalAuthPlugin(Registrar registrar) { - this.registrar = registrar; - this.registrar.addActivityResultListener(resultListener); - } - + @Override public void onMethodCall(MethodCall call, final Result result) { this.result = result; From 69455dafa8e26f9a20e0e21c9536f02aaeff3806 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Mon, 4 Jan 2021 21:35:13 -0600 Subject: [PATCH 37/65] Use `authenticate` as the primary method and remove redundant code --- .../plugins/localauth/LocalAuthPlugin.java | 7 +-- .../ios/Classes/FLTLocalAuthPlugin.m | 10 +++-- packages/local_auth/lib/local_auth.dart | 45 +++++++------------ 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 7e11d12ab449..d7284fb1bdc3 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -91,6 +91,7 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) { public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); final LocalAuthPlugin plugin = new LocalAuthPlugin(); + plugin.registrar = registrar; registrar.addActivityResultListener(plugin.resultListener); channel.setMethodCallHandler(plugin); } @@ -107,7 +108,6 @@ public void onMethodCall(MethodCall call, final Result result) { this.result = result; switch (call.method) { case "authenticate": - case "authenticateWithBiometrics": this.authenticate(call, result); break; case "getAvailableBiometrics": @@ -176,8 +176,9 @@ public void onError(String code, String error) { } }; - // let authenticateWithBiometrics try biometric prompt - might not work - if (call.method.equals("authenticateWithBiometrics")) { + // if is biometricOnly try biometric prompt - might not work + boolean isBiometricOnly = call.argument("biometricOnly"); + if (isBiometricOnly) { if (!canAuthenticateWithBiometrics()) { if (!hasBiometricHardware()) { completionHandler.onError("NoHardware", "No biometric hardware found"); diff --git a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m index 90da886e8781..81e77b61bb5f 100644 --- a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m @@ -22,10 +22,12 @@ + (void)registerWithRegistrar:(NSObject *)registrar { } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"authenticateWithBiometrics" isEqualToString:call.method]) { - [self authenticateWithBiometrics:call.arguments withFlutterResult:result]; - } else if ([@"authenticate" isEqualToString:call.method]) { - [self authenticate:call.arguments withFlutterResult:result]; + if ([@"authenticate" isEqualToString:call.method]) { + if (arguments[@"biometricOnly"]) { + [self authenticateWithBiometrics:call.arguments withFlutterResult:result]; + } else { + [self authenticate:call.arguments withFlutterResult:result]; + } } else if ([@"getAvailableBiometrics" isEqualToString:call.method]) { [self getAvailableBiometrics:result]; } else if ([@"isDeviceSupported" isEqualToString:call.method]) { diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index 06818609ffbc..b8cbcd0bb55d 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -73,29 +73,16 @@ class LocalAuthentication { AndroidAuthMessages androidAuthStrings = const AndroidAuthMessages(), IOSAuthMessages iOSAuthStrings = const IOSAuthMessages(), bool sensitiveTransaction = true, - }) async { - assert(localizedReason != null); - final Map args = { - 'localizedReason': localizedReason, - 'useErrorDialogs': useErrorDialogs, - 'stickyAuth': stickyAuth, - 'sensitiveTransaction': sensitiveTransaction, - }; - if (_platform.isIOS) { - args.addAll(iOSAuthStrings.args); - } else if (_platform.isAndroid) { - args.addAll(androidAuthStrings.args); - } else { - throw PlatformException( - code: otherOperatingSystem, - message: 'Local authentication does not support non-Android/iOS ' - 'operating systems.', - details: 'Your operating system is ${_platform.operatingSystem}'); - } - return (await _channel.invokeMethod( - 'authenticateWithBiometrics', args)) ?? - false; - } + }) => + authenticate( + localizedReason: localizedReason, + useErrorDialogs: useErrorDialogs, + stickyAuth: stickyAuth, + androidAuthStrings: androidAuthStrings, + iOSAuthStrings: iOSAuthStrings, + sensitiveTransaction: sensitiveTransaction, + biometricOnly: true, + ); /// Authenticates the user with biometrics available on the device while also /// allowing the user to use device authentication - pin, pattern, passcode. @@ -130,6 +117,9 @@ class LocalAuthentication { /// dialog after the face is recognized to make sure the user meant to unlock /// their phone. /// + /// Setting [biometricOnly] to true prevents authenticates from using non-biometric + /// local authentication such as pin, passcode, and passcode. + /// /// Throws an [PlatformException] if there were technical problems with local /// authentication (e.g. lack of relevant hardware). This might throw /// [PlatformException] with error code [otherOperatingSystem] on the iOS @@ -141,6 +131,7 @@ class LocalAuthentication { AndroidAuthMessages androidAuthStrings = const AndroidAuthMessages(), IOSAuthMessages iOSAuthStrings = const IOSAuthMessages(), bool sensitiveTransaction = true, + bool biometricOnly = false, }) async { assert(localizedReason != null); final Map args = { @@ -148,6 +139,7 @@ class LocalAuthentication { 'useErrorDialogs': useErrorDialogs, 'stickyAuth': stickyAuth, 'sensitiveTransaction': sensitiveTransaction, + 'biometricOnly': biometricOnly, }; if (_platform.isIOS) { args.addAll(iOSAuthStrings.args); @@ -179,16 +171,13 @@ class LocalAuthentication { /// Returns true if device is capable of checking biometrics /// /// Returns a [Future] bool true or false: - Future get canCheckBiometrics async => - (await _channel.invokeListMethod('getAvailableBiometrics'))! - .isNotEmpty; + Future get canCheckBiometrics async => (await _channel.invokeListMethod('getAvailableBiometrics'))!.isNotEmpty; /// Returns true if device is capable of checking biometrics or is able to /// fail over to device credentials. /// /// Returns a [Future] bool true or false: - Future isDeviceSupported() async => - (await _channel.invokeMethod('isDeviceSupported')) ?? false; + Future isDeviceSupported() async => (await _channel.invokeMethod('isDeviceSupported')) ?? false; /// Returns a list of enrolled biometrics /// From df8348f08658b04333d07da83cffc4a813e7ce80 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Mon, 4 Jan 2021 21:35:51 -0600 Subject: [PATCH 38/65] Update gradle --- packages/integration_test/example/android/build.gradle | 2 +- .../example/android/gradle/wrapper/gradle-wrapper.properties | 2 +- packages/local_auth/android/build.gradle | 2 +- .../example/android/gradle/wrapper/gradle-wrapper.properties | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/integration_test/example/android/build.gradle b/packages/integration_test/example/android/build.gradle index 11e3d0901a2e..ea78cdf2c29c 100644 --- a/packages/integration_test/example/android/build.gradle +++ b/packages/integration_test/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.1.1' } } diff --git a/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties index bc24dcf039a5..90f271dfdedc 100644 --- a/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/packages/local_auth/android/build.gradle b/packages/local_auth/android/build.gradle index 0fc603c36867..ae8e6f2828a2 100644 --- a/packages/local_auth/android/build.gradle +++ b/packages/local_auth/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.0' + classpath 'com.android.tools.build:gradle:4.1.1' } } diff --git a/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties index 96602d2aa859..cd9fe1c68282 100644 --- a/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 12 18:20:06 CDT 2020 +#Sun Jan 03 14:07:08 CST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip From 8f5c1b5790fe8841b7ba493e0e070b99a96bb21e Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Mon, 4 Jan 2021 21:54:04 -0600 Subject: [PATCH 39/65] Format code --- .../java/io/flutter/plugins/localauth/LocalAuthPlugin.java | 2 +- packages/local_auth/lib/local_auth.dart | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index d7284fb1bdc3..f3c302f9f6e8 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -102,7 +102,7 @@ public static void registerWith(Registrar registrar) { *

Use this constructor when adding this plugin to an app with v2 embedding. */ public LocalAuthPlugin() {} - + @Override public void onMethodCall(MethodCall call, final Result result) { this.result = result; diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index b8cbcd0bb55d..19527cf23800 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -171,13 +171,16 @@ class LocalAuthentication { /// Returns true if device is capable of checking biometrics /// /// Returns a [Future] bool true or false: - Future get canCheckBiometrics async => (await _channel.invokeListMethod('getAvailableBiometrics'))!.isNotEmpty; + Future get canCheckBiometrics async => + (await _channel.invokeListMethod('getAvailableBiometrics'))! + .isNotEmpty; /// Returns true if device is capable of checking biometrics or is able to /// fail over to device credentials. /// /// Returns a [Future] bool true or false: - Future isDeviceSupported() async => (await _channel.invokeMethod('isDeviceSupported')) ?? false; + Future isDeviceSupported() async => + (await _channel.invokeMethod('isDeviceSupported')) ?? false; /// Returns a list of enrolled biometrics /// From a76349dedcf9808bb5cd2bb1af090dee7e496c8e Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Mon, 4 Jan 2021 22:01:39 -0600 Subject: [PATCH 40/65] Fix tests to include 'biometricOnly' parameter --- packages/local_auth/test/local_auth_test.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/local_auth/test/local_auth_test.dart b/packages/local_auth/test/local_auth_test.dart index 642ae5b73a1e..ecfee6e52dd1 100644 --- a/packages/local_auth/test/local_auth_test.dart +++ b/packages/local_auth/test/local_auth_test.dart @@ -39,12 +39,13 @@ void main() { expect( log, [ - isMethodCall('authenticateWithBiometrics', + isMethodCall('authenticate', arguments: { 'localizedReason': 'Needs secure', 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, + 'biometricOnly': true, }..addAll(const AndroidAuthMessages().args)), ], ); @@ -58,12 +59,13 @@ void main() { expect( log, [ - isMethodCall('authenticateWithBiometrics', + isMethodCall('authenticate', arguments: { 'localizedReason': 'Needs secure', 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, + 'biometricOnly': true, }..addAll(const IOSAuthMessages().args)), ], ); @@ -79,12 +81,13 @@ void main() { expect( log, [ - isMethodCall('authenticateWithBiometrics', + isMethodCall('authenticate', arguments: { 'localizedReason': 'Insecure', 'useErrorDialogs': false, 'stickyAuth': false, 'sensitiveTransaction': false, + 'biometricOnly': true, }..addAll(const AndroidAuthMessages().args)), ], ); @@ -106,6 +109,7 @@ void main() { 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, + 'biometricOnly': false, }..addAll(const AndroidAuthMessages().args)), ], ); @@ -125,6 +129,7 @@ void main() { 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, + 'biometricOnly': false, }..addAll(const IOSAuthMessages().args)), ], ); @@ -146,6 +151,7 @@ void main() { 'useErrorDialogs': false, 'stickyAuth': false, 'sensitiveTransaction': false, + 'biometricOnly': false, }..addAll(const AndroidAuthMessages().args)), ], ); From 3b8bbe6ecc02d01e7f0142b6134de28320b6f9bd Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 08:19:11 -0600 Subject: [PATCH 41/65] Use `call.arguments`instead of `arguments` --- packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m index 81e77b61bb5f..bda16700bbf7 100644 --- a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m @@ -23,7 +23,7 @@ + (void)registerWithRegistrar:(NSObject *)registrar { - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"authenticate" isEqualToString:call.method]) { - if (arguments[@"biometricOnly"]) { + if (call.arguments[@"biometricOnly"]) { [self authenticateWithBiometrics:call.arguments withFlutterResult:result]; } else { [self authenticate:call.arguments withFlutterResult:result]; From 7d2f4c1067388ebc5a513de97599387d58943b5b Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 09:06:58 -0600 Subject: [PATCH 42/65] Cast boolValue --- packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m index bda16700bbf7..f0f763ae1cf4 100644 --- a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m @@ -23,7 +23,8 @@ + (void)registerWithRegistrar:(NSObject *)registrar { - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"authenticate" isEqualToString:call.method]) { - if (call.arguments[@"biometricOnly"]) { + bool isBiometricOnly = [call.arguments[@"biometricOnly"] boolValue] ; + if (isBiometricOnly) { [self authenticateWithBiometrics:call.arguments withFlutterResult:result]; } else { [self authenticate:call.arguments withFlutterResult:result]; @@ -144,7 +145,8 @@ - (void)authenticate:(NSDictionary *)arguments withFlutterResult:(FlutterResult) if (@available(iOS 9.0, *)) { if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) { - [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication + + [context evaluatePolicy:kLAPolicyDeviceOwnerAuthentication localizedReason:arguments[@"localizedReason"] reply:^(BOOL success, NSError *error) { if (success) { From 5a48b9b4a666f5b5a1ba64fcd79e3fadce72e427 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 09:21:08 -0600 Subject: [PATCH 43/65] Format code --- packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m index f0f763ae1cf4..cda49a7d68c3 100644 --- a/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m +++ b/packages/local_auth/ios/Classes/FLTLocalAuthPlugin.m @@ -23,7 +23,7 @@ + (void)registerWithRegistrar:(NSObject *)registrar { - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"authenticate" isEqualToString:call.method]) { - bool isBiometricOnly = [call.arguments[@"biometricOnly"] boolValue] ; + bool isBiometricOnly = [call.arguments[@"biometricOnly"] boolValue]; if (isBiometricOnly) { [self authenticateWithBiometrics:call.arguments withFlutterResult:result]; } else { @@ -145,7 +145,6 @@ - (void)authenticate:(NSDictionary *)arguments withFlutterResult:(FlutterResult) if (@available(iOS 9.0, *)) { if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) { - [context evaluatePolicy:kLAPolicyDeviceOwnerAuthentication localizedReason:arguments[@"localizedReason"] reply:^(BOOL success, NSError *error) { From 26d21ab0baadb2236942cd3bd407a699f19e9f65 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 09:54:52 -0600 Subject: [PATCH 44/65] Add null checks --- .../plugins/localauth/LocalAuthPlugin.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index f3c302f9f6e8..7fca8720e70b 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -301,18 +301,27 @@ private ArrayList getAvailableBiometrics() { } private boolean isDeviceSupported() { + Activity activity = getActivity(); + if (activity == null) return false; + KeyguardManager keyguardManager = - (KeyguardManager) getActivity().getBaseContext().getSystemService(KEYGUARD_SERVICE); + (KeyguardManager) activity.getBaseContext().getSystemService(KEYGUARD_SERVICE); return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure()); } private boolean canAuthenticateWithBiometrics() { - BiometricManager biometricManager = BiometricManager.from(getActivity()); + Activity activity = getActivity(); + if (activity == null) return false; + + BiometricManager biometricManager = BiometricManager.from(activity); return biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS; } private boolean hasBiometricHardware() { - BiometricManager biometricManager = BiometricManager.from(getActivity()); + Activity activity = getActivity(); + if (activity == null) return false; + + BiometricManager biometricManager = BiometricManager.from(activity); return biometricManager.canAuthenticate() != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; } From 50d60d1b42ac1d32e49c9f668321b1149df2bf93 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 10:51:39 -0600 Subject: [PATCH 45/65] Update gradle version --- packages/integration_test/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integration_test/android/build.gradle b/packages/integration_test/android/build.gradle index f35815281949..45f1cb3e5d40 100644 --- a/packages/integration_test/android/build.gradle +++ b/packages/integration_test/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.1.1' } } From d4439cda4eb7f80e372b63ee8774367d6152d2b4 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 11:53:39 -0600 Subject: [PATCH 46/65] Update gradle --- .../android/app/gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/local_auth/example/android/app/gradle/wrapper/gradle-wrapper.properties index 9a4163a4f5ee..186b71557c50 100644 --- a/packages/local_auth/example/android/app/gradle/wrapper/gradle-wrapper.properties +++ b/packages/local_auth/example/android/app/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 3f40e6c560a6c57e4b785e1c56b094e569c883e5 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 19:08:01 -0600 Subject: [PATCH 47/65] Set service properties onAttachedToActivity --- .../plugins/localauth/LocalAuthPlugin.java | 70 +++++++------------ 1 file changed, 27 insertions(+), 43 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 7fca8720e70b..c5de57a1bc31 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -40,20 +40,7 @@ public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware { private static final String CHANNEL_NAME = "plugins.flutter.io/local_auth"; private static final int LOCK_REQUEST_CODE = 221; - - private ActivityPluginBinding activityPluginBinding; - private Registrar registrar; - - private Activity getActivity() { - if (activityPluginBinding != null) { - return activityPluginBinding.getActivity(); - } - if (registrar != null) { - return registrar.activity(); - } - return null; - } - + private Activity activity; private final AtomicBoolean authInProgress = new AtomicBoolean(false); private AuthenticationHelper authHelper; @@ -61,6 +48,9 @@ private Activity getActivity() { private MethodChannel channel; private Lifecycle lifecycle; private Result result; + private BiometricManager biometricManager; + private FingerprintManager fingerprintManager; + private KeyguardManager keyguardManager; final PluginRegistry.ActivityResultListener resultListener = new PluginRegistry.ActivityResultListener() { @@ -91,8 +81,8 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) { public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); final LocalAuthPlugin plugin = new LocalAuthPlugin(); - plugin.registrar = registrar; registrar.addActivityResultListener(plugin.resultListener); + plugin.activity = registrar.activity(); channel.setMethodCallHandler(plugin); } @@ -134,8 +124,6 @@ private void authenticate(MethodCall call, final Result result) { return; } - Activity activity = getActivity(); - if (activity == null || activity.isFinishing()) { result.error("no_activity", "local_auth plugin requires a foreground activity", null); return; @@ -202,12 +190,8 @@ public void onError(String code, String error) { return; } - Context context = activity.getApplicationContext(); - // API 23 - 28 with fingerprint - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - FingerprintManager fingerprintManager = - (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && fingerprintManager != null) { if (fingerprintManager.hasEnrolledFingerprints()) { authHelper = new AuthenticationHelper( @@ -218,8 +202,9 @@ public void onError(String code, String error) { } // API 23 or higher with device credentials - KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && keyguardManager != null + && keyguardManager.isDeviceSecure()) { String title = call.argument("signInTitle"); String reason = call.argument("localizedReason"); Intent authIntent = keyguardManager.createConfirmDeviceCredentialIntent(title, reason); @@ -264,7 +249,6 @@ private void stopAuthentication() { */ private void getAvailableBiometrics(final Result result) { try { - Activity activity = getActivity(); if (activity == null || activity.isFinishing()) { result.error("no_activity", "local_auth plugin requires a foreground activity", null); return; @@ -278,7 +262,6 @@ private void getAvailableBiometrics(final Result result) { private ArrayList getAvailableBiometrics() { ArrayList biometrics = new ArrayList(); - Activity activity = getActivity(); if (activity == null || activity.isFinishing()) { return biometrics; } @@ -301,27 +284,17 @@ private ArrayList getAvailableBiometrics() { } private boolean isDeviceSupported() { - Activity activity = getActivity(); - if (activity == null) return false; - - KeyguardManager keyguardManager = - (KeyguardManager) activity.getBaseContext().getSystemService(KEYGUARD_SERVICE); + if (keyguardManager == null) return false; return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure()); } private boolean canAuthenticateWithBiometrics() { - Activity activity = getActivity(); - if (activity == null) return false; - - BiometricManager biometricManager = BiometricManager.from(activity); + if (biometricManager == null) return false; return biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS; } private boolean hasBiometricHardware() { - Activity activity = getActivity(); - if (activity == null) return false; - - BiometricManager biometricManager = BiometricManager.from(activity); + if (biometricManager == null) return false; return biometricManager.canAuthenticate() != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; } @@ -337,9 +310,22 @@ public void onAttachedToEngine(FlutterPluginBinding binding) { @Override public void onDetachedFromEngine(FlutterPluginBinding binding) {} + private void setServicesFromActivity(Activity activity) { + if (activity == null) return; + this.activity = activity; + Context context = activity.getBaseContext(); + biometricManager = BiometricManager.from(activity); + keyguardManager = + (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + fingerprintManager = + (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); + } + } + @Override public void onAttachedToActivity(ActivityPluginBinding binding) { - activityPluginBinding = binding; + setServicesFromActivity(binding.getActivity()); binding.addActivityResultListener(resultListener); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); channel.setMethodCallHandler(this); @@ -348,19 +334,17 @@ public void onAttachedToActivity(ActivityPluginBinding binding) { @Override public void onDetachedFromActivityForConfigChanges() { lifecycle = null; - activityPluginBinding = null; } @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { - activityPluginBinding = binding; + setServicesFromActivity(binding.getActivity()); binding.addActivityResultListener(resultListener); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); } @Override public void onDetachedFromActivity() { - activityPluginBinding = null; lifecycle = null; channel.setMethodCallHandler(null); } From 2d1b4ba711fbe0e23e050cdd0f33118164cd0f01 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 19:10:19 -0600 Subject: [PATCH 48/65] Show errors --- packages/local_auth/example/lib/main.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index f0dd402a3712..65a2c23df564 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -21,7 +21,6 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { final LocalAuthentication auth = LocalAuthentication(); - _SupportState _supportState = _SupportState.unknown; bool? _canCheckBiometrics; List? _availableBiometrics; @@ -85,6 +84,11 @@ class _MyAppState extends State { }); } on PlatformException catch (e) { print(e); + setState(() { + _isAuthenticating = false; + _authorized = "Error - ${e.message}"; + }); + return; } if (!mounted) return; @@ -100,7 +104,8 @@ class _MyAppState extends State { _authorized = 'Authenticating'; }); authenticated = await auth.authenticateWithBiometrics( - localizedReason: 'Scan your fingerprint to authenticate', + localizedReason: + 'Scan your fingerprint (or face or whatever) to authenticate', useErrorDialogs: true, stickyAuth: true); setState(() { @@ -109,6 +114,11 @@ class _MyAppState extends State { }); } on PlatformException catch (e) { print(e); + setState(() { + _isAuthenticating = false; + _authorized = "Error - ${e.message}"; + }); + return; } if (!mounted) return; From 54995ea9da4e09b141741a190f44d0e92ed474c0 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 19:11:00 -0600 Subject: [PATCH 49/65] Format --- .../java/io/flutter/plugins/localauth/LocalAuthPlugin.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index c5de57a1bc31..38e3204104ff 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -315,8 +315,7 @@ private void setServicesFromActivity(Activity activity) { this.activity = activity; Context context = activity.getBaseContext(); biometricManager = BiometricManager.from(activity); - keyguardManager = - (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE); + keyguardManager = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); From e65260fcb40ca6ff4efb899a7d8ce3f564d9bb28 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 19:12:08 -0600 Subject: [PATCH 50/65] Remove mystery hack of forgotten days gone by --- .../io/flutter/plugins/localauth/AuthenticationHelper.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index 65e4887d195a..096c7efd6d3d 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -225,12 +225,9 @@ public void onResume(@NonNull LifecycleOwner owner) { onActivityResumed(null); } - // hack: dialog opens twice on Android 8 - private boolean isSettingsDialogOpen = false; // Suppress inflateParams lint because dialogs do not need to attach to a parent view. @SuppressLint("InflateParams") private void showGoToSettingsDialog(String title, String descriptionText) { - if (isSettingsDialogOpen) return; View view = LayoutInflater.from(activity).inflate(R.layout.go_to_setting, null, false); TextView message = (TextView) view.findViewById(R.id.fingerprint_required); TextView description = (TextView) view.findViewById(R.id.go_to_setting_description); @@ -241,7 +238,6 @@ private void showGoToSettingsDialog(String title, String descriptionText) { new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - isSettingsDialogOpen = false; completionHandler.onFailure(); stop(); activity.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS)); @@ -251,12 +247,10 @@ public void onClick(DialogInterface dialog, int which) { new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - isSettingsDialogOpen = false; completionHandler.onFailure(); stop(); } }; - isSettingsDialogOpen = true; new AlertDialog.Builder(context) .setView(view) .setPositiveButton((String) call.argument("goToSetting"), goToSettingHandler) From 454edbd632e129a0d220481beda18ac7fe90c5c1 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 5 Jan 2021 22:14:30 -0600 Subject: [PATCH 51/65] Add gradle file --- .../android/gradle/wrapper/gradle-wrapper.properties | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/integration_test/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/integration_test/android/gradle/wrapper/gradle-wrapper.properties b/packages/integration_test/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..fec1007655a7 --- /dev/null +++ b/packages/integration_test/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip \ No newline at end of file From ee9af37f4a80d6e3d968b018f9f8bbffe38018f2 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 12 Jan 2021 14:19:43 -0600 Subject: [PATCH 52/65] Remove unreleased feature note from 0.6.3+1 --- packages/local_auth/CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/local_auth/CHANGELOG.md b/packages/local_auth/CHANGELOG.md index 2e2e6032ec1b..9c7199d0e449 100644 --- a/packages/local_auth/CHANGELOG.md +++ b/packages/local_auth/CHANGELOG.md @@ -37,10 +37,8 @@ ## 0.6.3+1 -* Add `authenticate` method that uses biometric authentication, but allows users to also use device authentication - pin, pattern, passcode. * Update package:e2e -> package:integration_test - ## 0.6.3 * Increase upper range of `package:platform` constraint to allow 3.X versions. @@ -72,12 +70,10 @@ * Replace deprecated `getFlutterEngine` call on Android. - ## 0.6.1+3 * Make the pedantic dev_dependency explicit. - ## 0.6.1+2 * Support v2 embedding. From a59f5d86baa40750c3a9781517724692b1ca071d Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 12 Jan 2021 14:21:45 -0600 Subject: [PATCH 53/65] Update changelog --- packages/local_auth/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/CHANGELOG.md b/packages/local_auth/CHANGELOG.md index 9c7199d0e449..46a5ed8f28cd 100644 --- a/packages/local_auth/CHANGELOG.md +++ b/packages/local_auth/CHANGELOG.md @@ -1,6 +1,6 @@ ## 1.1.0-nullsafety -* Allow pin, passcode, and pattern authentication +* Allow pin, passcode, and pattern authentication with `authenticate` method * **Breaking change**. Parameter names refactored to use the generic `biometric` prefix in place of `fingerprint` in the `AndroidAuthMessages` class * `fingerprintHint` is now `biometricHint` * `fingerprintNotRecognized`is now `biometricNotRecognized` From d2a0c0cbd612832f8dc71d6dfc2bbebd7e4bf475 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 12 Jan 2021 19:29:41 -0600 Subject: [PATCH 54/65] Remove local result properties and result listener --- .../plugins/localauth/LocalAuthPlugin.java | 43 ++++++------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 38e3204104ff..a4634eab718c 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -4,7 +4,6 @@ package io.flutter.plugins.localauth; -import static android.app.Activity.RESULT_OK; import static android.content.Context.KEYGUARD_SERVICE; import android.app.Activity; @@ -14,6 +13,8 @@ import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; + +import androidx.annotation.NonNull; import androidx.biometric.BiometricManager; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.Lifecycle; @@ -25,7 +26,6 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugins.localauth.AuthenticationHelper.AuthCompletionHandler; import java.util.ArrayList; @@ -47,26 +47,10 @@ public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, Activi // These are null when not using v2 embedding. private MethodChannel channel; private Lifecycle lifecycle; - private Result result; private BiometricManager biometricManager; private FingerprintManager fingerprintManager; private KeyguardManager keyguardManager; - final PluginRegistry.ActivityResultListener resultListener = - new PluginRegistry.ActivityResultListener() { - @Override - public boolean onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == LOCK_REQUEST_CODE) { - if (resultCode == RESULT_OK) { - authenticateSuccess(); - } else { - authenticateFail(); - } - } - return false; - } - }; - /** * Registers a plugin with the v1 embedding api {@code io.flutter.plugin.common}. * @@ -81,7 +65,6 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) { public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); final LocalAuthPlugin plugin = new LocalAuthPlugin(); - registrar.addActivityResultListener(plugin.resultListener); plugin.activity = registrar.activity(); channel.setMethodCallHandler(plugin); } @@ -94,8 +77,7 @@ public static void registerWith(Registrar registrar) { public LocalAuthPlugin() {} @Override - public void onMethodCall(MethodCall call, final Result result) { - this.result = result; + public void onMethodCall(MethodCall call, @NonNull final Result result) { switch (call.method) { case "authenticate": this.authenticate(call, result); @@ -107,7 +89,7 @@ public void onMethodCall(MethodCall call, final Result result) { this.isDeviceSupported(result); break; case "stopAuthentication": - this.stopAuthentication(); + this.stopAuthentication(result); break; default: result.notImplemented(); @@ -148,12 +130,12 @@ private void authenticate(MethodCall call, final Result result) { new AuthCompletionHandler() { @Override public void onSuccess() { - authenticateSuccess(); + authenticateSuccess(result); } @Override public void onFailure() { - authenticateFail(); + authenticateFail(result); } @Override @@ -216,13 +198,13 @@ public void onError(String code, String error) { result.error("NotSupported", "This device does not support required security features", null); } - private void authenticateSuccess() { + private void authenticateSuccess(Result result) { if (authInProgress.compareAndSet(true, false)) { result.success(true); } } - private void authenticateFail() { + private void authenticateFail(Result result) { if (authInProgress.compareAndSet(true, false)) { result.success(false); } @@ -231,7 +213,7 @@ private void authenticateFail() { /* * Stops the authentication if in progress. */ - private void stopAuthentication() { + private void stopAuthentication(Result result) { try { if (authHelper != null && authInProgress.get()) { authHelper.stopAuthentication(); @@ -261,7 +243,7 @@ private void getAvailableBiometrics(final Result result) { } private ArrayList getAvailableBiometrics() { - ArrayList biometrics = new ArrayList(); + ArrayList biometrics = new ArrayList<>(); if (activity == null || activity.isFinishing()) { return biometrics; } @@ -305,10 +287,11 @@ private void isDeviceSupported(Result result) { @Override public void onAttachedToEngine(FlutterPluginBinding binding) { channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), CHANNEL_NAME); + channel.setMethodCallHandler(this); } @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) {} + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {} private void setServicesFromActivity(Activity activity) { if (activity == null) return; @@ -325,7 +308,6 @@ private void setServicesFromActivity(Activity activity) { @Override public void onAttachedToActivity(ActivityPluginBinding binding) { setServicesFromActivity(binding.getActivity()); - binding.addActivityResultListener(resultListener); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); channel.setMethodCallHandler(this); } @@ -338,7 +320,6 @@ public void onDetachedFromActivityForConfigChanges() { @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { setServicesFromActivity(binding.getActivity()); - binding.addActivityResultListener(resultListener); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); } From 2d184cdc43886a3a83fbc019aad1b4b32761d217 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 12 Jan 2021 19:32:36 -0600 Subject: [PATCH 55/65] Remove unneeded this. --- .../io/flutter/plugins/localauth/LocalAuthPlugin.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index a4634eab718c..382a6e583180 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -80,16 +80,16 @@ public LocalAuthPlugin() {} public void onMethodCall(MethodCall call, @NonNull final Result result) { switch (call.method) { case "authenticate": - this.authenticate(call, result); + authenticate(call, result); break; case "getAvailableBiometrics": - this.getAvailableBiometrics(result); + getAvailableBiometrics(result); break; case "isDeviceSupported": - this.isDeviceSupported(result); + isDeviceSupported(result); break; case "stopAuthentication": - this.stopAuthentication(result); + stopAuthentication(result); break; default: result.notImplemented(); From 0c199f61d1e5b717ad76d1f25a86ecfdbbb81d0d Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 12 Jan 2021 19:48:43 -0600 Subject: [PATCH 56/65] Add deprecated annotation to authenticateWithBiometrics --- packages/local_auth/lib/local_auth.dart | 37 +------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index 19527cf23800..7459a3423a73 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -30,42 +30,7 @@ void setMockPathProviderPlatform(Platform platform) { /// A Flutter plugin for authenticating the user identity locally. class LocalAuthentication { - /// Authenticates the user with biometrics available on the device. - /// - /// Returns a [Future] holding true, if the user successfully authenticated, - /// false otherwise. - /// - /// [localizedReason] is the message to show to user while prompting them - /// for authentication. This is typically along the lines of: 'Please scan - /// your finger to access MyApp.' - /// - /// [useErrorDialogs] = true means the system will attempt to handle user - /// fixable issues encountered while authenticating. For instance, if - /// fingerprint reader exists on the phone but there's no fingerprint - /// registered, the plugin will attempt to take the user to settings to add - /// one. Anything that is not user fixable, such as no biometric sensor on - /// device, will be returned as a [PlatformException]. - /// - /// [stickyAuth] is used when the application goes into background for any - /// reason while the authentication is in progress. Due to security reasons, - /// the authentication has to be stopped at that time. If stickyAuth is set - /// to true, authentication resumes when the app is resumed. If it is set to - /// false (default), then as soon as app is paused a failure message is sent - /// back to Dart and it is up to the client app to restart authentication or - /// do something else. - /// - /// Construct [AndroidAuthStrings] and [IOSAuthStrings] if you want to - /// customize messages in the dialogs. - /// - /// Setting [sensitiveTransaction] to true enables platform specific - /// precautions. For instance, on face unlock, Android opens a confirmation - /// dialog after the face is recognized to make sure the user meant to unlock - /// their phone. - /// - /// Throws an [PlatformException] if there were technical problems with local - /// authentication (e.g. lack of relevant hardware). This might throw - /// [PlatformException] with error code [otherOperatingSystem] on the iOS - /// simulator. + @Deprecated("Use `authenticate` with `biometricOnly: true` instead") Future authenticateWithBiometrics({ required String localizedReason, bool useErrorDialogs = true, From 34dac801dc20a32b45a4d88e899970b012430d95 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Tue, 12 Jan 2021 19:48:59 -0600 Subject: [PATCH 57/65] Format code --- .../main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 382a6e583180..3cf064c04afb 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -13,7 +13,6 @@ import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; - import androidx.annotation.NonNull; import androidx.biometric.BiometricManager; import androidx.fragment.app.FragmentActivity; From 028d966aec17190e49098b2c7814456727355a88 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Wed, 13 Jan 2021 09:18:29 -0600 Subject: [PATCH 58/65] Add doc comment --- packages/local_auth/lib/local_auth.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index 7459a3423a73..5a1fe1a8eacd 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -30,6 +30,8 @@ void setMockPathProviderPlatform(Platform platform) { /// A Flutter plugin for authenticating the user identity locally. class LocalAuthentication { + /// The `authenticateWithBiometrics` method has been deprecated. + /// Use `authenticate` with `biometricOnly: true` instead @Deprecated("Use `authenticate` with `biometricOnly: true` instead") Future authenticateWithBiometrics({ required String localizedReason, @@ -136,16 +138,13 @@ class LocalAuthentication { /// Returns true if device is capable of checking biometrics /// /// Returns a [Future] bool true or false: - Future get canCheckBiometrics async => - (await _channel.invokeListMethod('getAvailableBiometrics'))! - .isNotEmpty; + Future get canCheckBiometrics async => (await _channel.invokeListMethod('getAvailableBiometrics'))!.isNotEmpty; /// Returns true if device is capable of checking biometrics or is able to /// fail over to device credentials. /// /// Returns a [Future] bool true or false: - Future isDeviceSupported() async => - (await _channel.invokeMethod('isDeviceSupported')) ?? false; + Future isDeviceSupported() async => (await _channel.invokeMethod('isDeviceSupported')) ?? false; /// Returns a list of enrolled biometrics /// From 3f8d1631e342c16b62cbd163f5ec3c3b8652db07 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Wed, 13 Jan 2021 09:25:29 -0600 Subject: [PATCH 59/65] Update readme --- packages/local_auth/README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/local_auth/README.md b/packages/local_auth/README.md index ff0401ec4cc8..80820d759fec 100644 --- a/packages/local_auth/README.md +++ b/packages/local_auth/README.md @@ -65,25 +65,23 @@ bool didAuthenticate = localizedReason: 'Please authenticate to show account balance'); ``` -The `authenticateWithBiometrics()` method uses biometric authentication only. +To authenticate using biometric authentication only, set `biometricOnly` to `true`. ```dart var localAuth = LocalAuthentication(); bool didAuthenticate = - await localAuth.authenticateWithBiometrics( - localizedReason: 'Please authenticate to show account balance'); + await localAuth.authenticate( + localizedReason: 'Please authenticate to show account balance', + biometricOnly: true); ``` -Note that `authenticate()` and `authenticateWithBiometrics()` methods have -the same signature and parameters. - If you don't want to use the default dialogs, call this API with 'useErrorDialogs = false'. In this case, it will throw the error message back and you need to handle them in your dart code: ```dart bool didAuthenticate = - await localAuth.authenticateWithBiometrics( + await localAuth.authenticate( localizedReason: 'Please authenticate to show account balance', useErrorDialogs: false); ``` @@ -99,7 +97,7 @@ const iosStrings = const IOSAuthMessages( goToSettingsButton: 'settings', goToSettingsDescription: 'Please set up your Touch ID.', lockOut: 'Please reenable your Touch ID'); -await localAuth.authenticateWithBiometrics( +await localAuth.authenticate( localizedReason: 'Please authenticate to show account balance', useErrorDialogs: false, iOSAuthStrings: iosStrings); @@ -127,7 +125,7 @@ import 'package:flutter/services.dart'; import 'package:local_auth/error_codes.dart' as auth_error; try { - bool didAuthenticate = await local_auth.authenticateWithBiometrics( + bool didAuthenticate = await local_auth.authenticate( localizedReason: 'Please authenticate to show account balance'); } on PlatformException catch (e) { if (e.code == auth_error.notAvailable) { @@ -205,7 +203,7 @@ Update your project's `AndroidManifest.xml` file to include the On Android, you can check only for existence of fingerprint hardware prior to API 29 (Android Q). Therefore, if you would like to support other biometrics types (such as face scanning) and you want to support SDKs lower than Q, -_do not_ call `getAvailableBiometrics`. Simply call `authenticateWithBiometrics`. +_do not_ call `getAvailableBiometrics`. Simply call `authenticate` with `biometricOnly: true`. This will return an error if there was no hardware available. ## Sticky Auth From e0facb5733825436fd7784c1c191c6cd9eab00b8 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Wed, 13 Jan 2021 09:47:24 -0600 Subject: [PATCH 60/65] Format code --- packages/local_auth/lib/local_auth.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index 5a1fe1a8eacd..f8a7228bcd8d 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -138,13 +138,16 @@ class LocalAuthentication { /// Returns true if device is capable of checking biometrics /// /// Returns a [Future] bool true or false: - Future get canCheckBiometrics async => (await _channel.invokeListMethod('getAvailableBiometrics'))!.isNotEmpty; + Future get canCheckBiometrics async => + (await _channel.invokeListMethod('getAvailableBiometrics'))! + .isNotEmpty; /// Returns true if device is capable of checking biometrics or is able to /// fail over to device credentials. /// /// Returns a [Future] bool true or false: - Future isDeviceSupported() async => (await _channel.invokeMethod('isDeviceSupported')) ?? false; + Future isDeviceSupported() async => + (await _channel.invokeMethod('isDeviceSupported')) ?? false; /// Returns a list of enrolled biometrics /// From d0eda354ed90ff22de3eec792aa84a8fffa82430 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Wed, 13 Jan 2021 19:44:33 -0600 Subject: [PATCH 61/65] Update deprecate code --- packages/local_auth/example/lib/main.dart | 5 +++-- packages/local_auth/lib/error_codes.dart | 2 +- packages/local_auth/test/local_auth_test.dart | 9 ++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index dab4b416b868..8cdad5b7eed3 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -103,11 +103,12 @@ class _MyAppState extends State { _isAuthenticating = true; _authorized = 'Authenticating'; }); - authenticated = await auth.authenticateWithBiometrics( + authenticated = await auth.authenticate( localizedReason: 'Scan your fingerprint (or face or whatever) to authenticate', useErrorDialogs: true, - stickyAuth: true); + stickyAuth: true, + biometricOnly: true); setState(() { _isAuthenticating = false; _authorized = 'Authenticating'; diff --git a/packages/local_auth/lib/error_codes.dart b/packages/local_auth/lib/error_codes.dart index 3f6f298ba4f3..3b080c13baf7 100644 --- a/packages/local_auth/lib/error_codes.dart +++ b/packages/local_auth/lib/error_codes.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. // Exception codes for `PlatformException` returned by -// `authenticateWithBiometrics`. +// `authenticate`. /// Indicates that the user has not yet configured a passcode (iOS) or /// PIN/pattern/password (Android) on the device. diff --git a/packages/local_auth/test/local_auth_test.dart b/packages/local_auth/test/local_auth_test.dart index ecfee6e52dd1..f4bf9fe0314d 100644 --- a/packages/local_auth/test/local_auth_test.dart +++ b/packages/local_auth/test/local_auth_test.dart @@ -33,8 +33,9 @@ void main() { group("With device auth fail over", () { test('authenticate with no args on Android.', () async { setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); - await localAuthentication.authenticateWithBiometrics( + await localAuthentication.authenticate( localizedReason: 'Needs secure', + biometricOnly: true, ); expect( log, @@ -53,8 +54,9 @@ void main() { test('authenticate with no args on iOS.', () async { setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); - await localAuthentication.authenticateWithBiometrics( + await localAuthentication.authenticate( localizedReason: 'Needs secure', + biometricOnly: true, ); expect( log, @@ -73,10 +75,11 @@ void main() { test('authenticate with no sensitive transaction.', () async { setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); - await localAuthentication.authenticateWithBiometrics( + await localAuthentication.authenticate( localizedReason: 'Insecure', sensitiveTransaction: false, useErrorDialogs: false, + biometricOnly: true, ); expect( log, From 3f3d3fae148e5cd37cb60e44e3b7bf7e4170c802 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Wed, 13 Jan 2021 20:04:16 -0600 Subject: [PATCH 62/65] Enable useAndroidX --- packages/integration_test/android/gradle.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/integration_test/android/gradle.properties b/packages/integration_test/android/gradle.properties index 2bd6f4fda009..755300e3a0b5 100644 --- a/packages/integration_test/android/gradle.properties +++ b/packages/integration_test/android/gradle.properties @@ -1,2 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true From 975e0c9662c60d382574e9c4288e883f30ef6ab8 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Wed, 13 Jan 2021 22:03:35 -0600 Subject: [PATCH 63/65] Revert changes to integration_test --- packages/integration_test/android/build.gradle | 2 +- packages/integration_test/android/gradle.properties | 3 --- .../android/gradle/wrapper/gradle-wrapper.properties | 5 ----- packages/integration_test/example/android/build.gradle | 2 +- .../example/android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../integration_test/example/android/settings_aar.gradle | 1 - 6 files changed, 3 insertions(+), 12 deletions(-) delete mode 100644 packages/integration_test/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 packages/integration_test/example/android/settings_aar.gradle diff --git a/packages/integration_test/android/build.gradle b/packages/integration_test/android/build.gradle index 45f1cb3e5d40..f35815281949 100644 --- a/packages/integration_test/android/build.gradle +++ b/packages/integration_test/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:3.6.3' } } diff --git a/packages/integration_test/android/gradle.properties b/packages/integration_test/android/gradle.properties index 755300e3a0b5..8bd86f680510 100644 --- a/packages/integration_test/android/gradle.properties +++ b/packages/integration_test/android/gradle.properties @@ -1,4 +1 @@ org.gradle.jvmargs=-Xmx1536M - -android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/integration_test/android/gradle/wrapper/gradle-wrapper.properties b/packages/integration_test/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index fec1007655a7..000000000000 --- a/packages/integration_test/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip \ No newline at end of file diff --git a/packages/integration_test/example/android/build.gradle b/packages/integration_test/example/android/build.gradle index ea78cdf2c29c..11e3d0901a2e 100644 --- a/packages/integration_test/example/android/build.gradle +++ b/packages/integration_test/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:3.6.3' } } diff --git a/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties index 90f271dfdedc..bc24dcf039a5 100644 --- a/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/packages/integration_test/example/android/settings_aar.gradle b/packages/integration_test/example/android/settings_aar.gradle deleted file mode 100644 index e7b4def49cb5..000000000000 --- a/packages/integration_test/example/android/settings_aar.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' From 7e27dec26ac6817b19411237917ad117c14b7870 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Wed, 13 Jan 2021 22:08:47 -0600 Subject: [PATCH 64/65] Update gradle.properties From bc323352f7740087165cae0fd0c9aa4c385c6899 Mon Sep 17 00:00:00 2001 From: Kenneth Jones Date: Thu, 14 Jan 2021 20:24:25 -0600 Subject: [PATCH 65/65] Add lockRequestResult --- .../plugins/localauth/LocalAuthPlugin.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 3cf064c04afb..f4c6c168f54d 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -4,6 +4,7 @@ package io.flutter.plugins.localauth; +import static android.app.Activity.RESULT_OK; import static android.content.Context.KEYGUARD_SERVICE; import android.app.Activity; @@ -25,6 +26,7 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugins.localauth.AuthenticationHelper.AuthCompletionHandler; import java.util.ArrayList; @@ -49,6 +51,22 @@ public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, Activi private BiometricManager biometricManager; private FingerprintManager fingerprintManager; private KeyguardManager keyguardManager; + private Result lockRequestResult; + private final PluginRegistry.ActivityResultListener resultListener = + new PluginRegistry.ActivityResultListener() { + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == LOCK_REQUEST_CODE) { + if (resultCode == RESULT_OK && lockRequestResult != null) { + authenticateSuccess(lockRequestResult); + } else { + authenticateFail(lockRequestResult); + } + lockRequestResult = null; + } + return false; + } + }; /** * Registers a plugin with the v1 embedding api {@code io.flutter.plugin.common}. @@ -66,6 +84,7 @@ public static void registerWith(Registrar registrar) { final LocalAuthPlugin plugin = new LocalAuthPlugin(); plugin.activity = registrar.activity(); channel.setMethodCallHandler(plugin); + registrar.addActivityResultListener(plugin.resultListener); } /** @@ -189,6 +208,9 @@ public void onError(String code, String error) { String title = call.argument("signInTitle"); String reason = call.argument("localizedReason"); Intent authIntent = keyguardManager.createConfirmDeviceCredentialIntent(title, reason); + + // save result for async response + lockRequestResult = result; activity.startActivityForResult(authIntent, LOCK_REQUEST_CODE); return; } @@ -306,6 +328,7 @@ private void setServicesFromActivity(Activity activity) { @Override public void onAttachedToActivity(ActivityPluginBinding binding) { + binding.addActivityResultListener(resultListener); setServicesFromActivity(binding.getActivity()); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); channel.setMethodCallHandler(this); @@ -318,6 +341,7 @@ public void onDetachedFromActivityForConfigChanges() { @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { + binding.addActivityResultListener(resultListener); setServicesFromActivity(binding.getActivity()); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); }