From 37c97713ed6cb1922a76e94cb846760d5e3e2d7a Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 20 Apr 2022 16:07:03 +0200 Subject: [PATCH 1/9] Fix getEnrolledBiometrics returning present biometrics that are not enrolled. --- .../local_auth_android/CHANGELOG.md | 5 + .../plugins/localauth/LocalAuthPlugin.java | 45 ++++--- .../plugins/localauth/LocalAuthTest.java | 127 ++++++++++++++++++ .../lib/local_auth_android.dart | 13 +- .../local_auth_android/pubspec.yaml | 2 +- .../test/local_auth_test.dart | 14 +- 6 files changed, 170 insertions(+), 36 deletions(-) diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index 7f198f2d66c0..b453cd40932e 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.0.1 + +* Fix getEnrolledBiometrics to be in line with iOS implementation: + Present biometrics that are not enrolled are no longer returned. + ## 1.0.0 * Initial release from migration to federated architecture. diff --git a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 49a6b788fe46..258324e3c8c3 100644 --- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -11,7 +11,6 @@ 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.annotation.NonNull; @@ -101,8 +100,8 @@ public void onMethodCall(MethodCall call, @NonNull final Result result) { case "authenticate": authenticate(call, result); break; - case "getAvailableBiometrics": - getAvailableBiometrics(result); + case "getEnrolledBiometrics": + getEnrolledBiometrics(result); break; case "isDeviceSupported": isDeviceSupported(result); @@ -251,39 +250,42 @@ private void stopAuthentication(Result result) { /* * Returns biometric types available on device */ - private void getAvailableBiometrics(final Result result) { + private void getEnrolledBiometrics(final Result result) { try { if (activity == null || activity.isFinishing()) { result.error("no_activity", "local_auth plugin requires a foreground activity", null); return; } - ArrayList biometrics = getAvailableBiometrics(); + ArrayList biometrics = getEnrolledBiometrics(); result.success(biometrics); } catch (Exception e) { result.error("no_biometrics_available", e.getMessage(), null); } } - private ArrayList getAvailableBiometrics() { + private ArrayList getEnrolledBiometrics() { ArrayList biometrics = new ArrayList<>(); 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 no hardware is present, an empty list is returned. + if (!hasBiometricHardware()) { + return biometrics; } - if (Build.VERSION.SDK_INT >= 29) { - if (packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) { - biometrics.add("face"); - } - if (packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)) { - biometrics.add("iris"); - } + // If no biometrics are enrolled, only "undefined" is returned. + if (!canAuthenticateWithBiometrics()) { + biometrics.add("undefined"); + return biometrics; + } + // If there are biometrics enrolled, the available ones are returned. + if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) + == BiometricManager.BIOMETRIC_SUCCESS) { + biometrics.add("weak"); + } + if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) + == BiometricManager.BIOMETRIC_SUCCESS) { + biometrics.add("strong"); } - return biometrics; } @@ -359,4 +361,9 @@ public void onDetachedFromActivity() { final Activity getActivity() { return activity; } + + @VisibleForTesting + void setBiometricManager(BiometricManager biometricManager) { + this.biometricManager = biometricManager; + } } diff --git a/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java b/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java index 41868e603ad8..3f1fffc9cb75 100644 --- a/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java +++ b/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java @@ -12,6 +12,7 @@ import android.app.Activity; import android.content.Context; +import androidx.biometric.BiometricManager; import androidx.lifecycle.Lifecycle; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.dart.DartExecutor; @@ -20,6 +21,8 @@ import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; +import java.util.ArrayList; +import java.util.Collections; import org.junit.Test; public class LocalAuthTest { @@ -61,4 +64,128 @@ public void onDetachedFromActivity_ShouldReleaseActivity() { plugin.onDetachedFromActivity(); assertNull(plugin.getActivity()); } + + @Test + public void getEnrolledBiometrics_shouldReturnError_whenNoActivity() { + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + + plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); + verify(mockResult) + .error("no_activity", "local_auth plugin requires a foreground activity", null); + } + + @Test + public void getEnrolledBiometrics_shouldReturnError_whenFinishingActivity() { + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + final Activity mockActivity = buildMockActivity(); + when(mockActivity.isFinishing()).thenReturn(true); + setPluginActivity(plugin, mockActivity); + + plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); + verify(mockResult) + .error("no_activity", "local_auth plugin requires a foreground activity", null); + } + + @Test + public void getEnrolledBiometrics_shouldReturnEmptyList_withoutHardwarePresent() { + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + setPluginActivity(plugin, buildMockActivity()); + final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + final BiometricManager mockBiometricManager = mock(BiometricManager.class); + when(mockBiometricManager.canAuthenticate()) + .thenReturn(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE); + plugin.setBiometricManager(mockBiometricManager); + + plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); + verify(mockResult).success(Collections.emptyList()); + } + + @Test + public void getEnrolledBiometrics_shouldReturnUndefined_withNoMethodsEnrolled() { + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + setPluginActivity(plugin, buildMockActivity()); + final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + final BiometricManager mockBiometricManager = mock(BiometricManager.class); + when(mockBiometricManager.canAuthenticate()) + .thenReturn(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED); + plugin.setBiometricManager(mockBiometricManager); + + plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); + verify(mockResult) + .success( + new ArrayList() { + { + add("undefined"); + } + }); + } + + @Test + public void getEnrolledBiometrics_shouldOnlyAddEnrolledBiometrics() { + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + setPluginActivity(plugin, buildMockActivity()); + final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + final BiometricManager mockBiometricManager = mock(BiometricManager.class); + when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) + .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); + when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) + .thenReturn(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED); + plugin.setBiometricManager(mockBiometricManager); + + plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); + verify(mockResult) + .success( + new ArrayList() { + { + add("weak"); + } + }); + } + + @Test + public void getEnrolledBiometrics_shouldAddStrongBiometrics() { + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + setPluginActivity(plugin, buildMockActivity()); + final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + final BiometricManager mockBiometricManager = mock(BiometricManager.class); + when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) + .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); + when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) + .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); + plugin.setBiometricManager(mockBiometricManager); + + plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); + verify(mockResult) + .success( + new ArrayList() { + { + add("weak"); + add("strong"); + } + }); + } + + private Activity buildMockActivity() { + final Activity mockActivity = mock(Activity.class); + final Context mockContext = mock(Context.class); + when(mockActivity.getBaseContext()).thenReturn(mockContext); + when(mockActivity.getApplicationContext()).thenReturn(mockContext); + return mockActivity; + } + + private void setPluginActivity(LocalAuthPlugin plugin, Activity activity) { + final HiddenLifecycleReference mockLifecycleReference = mock(HiddenLifecycleReference.class); + final FlutterPluginBinding mockPluginBinding = mock(FlutterPluginBinding.class); + final ActivityPluginBinding mockActivityBinding = mock(ActivityPluginBinding.class); + final FlutterEngine mockFlutterEngine = mock(FlutterEngine.class); + final DartExecutor mockDartExecutor = mock(DartExecutor.class); + when(mockPluginBinding.getFlutterEngine()).thenReturn(mockFlutterEngine); + when(mockFlutterEngine.getDartExecutor()).thenReturn(mockDartExecutor); + when(mockActivityBinding.getActivity()).thenReturn(activity); + when(mockActivityBinding.getLifecycle()).thenReturn(mockLifecycleReference); + plugin.onAttachedToEngine(mockPluginBinding); + plugin.onAttachedToActivity(mockActivityBinding); + } } diff --git a/packages/local_auth/local_auth_android/lib/local_auth_android.dart b/packages/local_auth/local_auth_android/lib/local_auth_android.dart index a3f314e3347b..539ecc80f213 100644 --- a/packages/local_auth/local_auth_android/lib/local_auth_android.dart +++ b/packages/local_auth/local_auth_android/lib/local_auth_android.dart @@ -55,20 +55,17 @@ class LocalAuthAndroid extends LocalAuthPlatform { @override Future> getEnrolledBiometrics() async { final List result = (await _channel.invokeListMethod( - 'getAvailableBiometrics', + 'getEnrolledBiometrics', )) ?? []; final List biometrics = []; for (final String value in result) { switch (value) { - case 'face': - biometrics.add(BiometricType.face); + case 'weak': + biometrics.add(BiometricType.weak); break; - case 'fingerprint': - biometrics.add(BiometricType.fingerprint); - break; - case 'iris': - biometrics.add(BiometricType.iris); + case 'strong': + biometrics.add(BiometricType.strong); break; case 'undefined': break; diff --git a/packages/local_auth/local_auth_android/pubspec.yaml b/packages/local_auth/local_auth_android/pubspec.yaml index ec2991db6a90..b6a404382b69 100644 --- a/packages/local_auth/local_auth_android/pubspec.yaml +++ b/packages/local_auth/local_auth_android/pubspec.yaml @@ -2,7 +2,7 @@ name: local_auth_android description: Android implementation of the local_auth plugin. repository: https://github.com/flutter/plugins/tree/master/packages/local_auth/local_auth_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 1.0.0 +version: 1.0.1 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/local_auth/local_auth_android/test/local_auth_test.dart b/packages/local_auth/local_auth_android/test/local_auth_test.dart index 31f5e5796445..d8f1e7ab1d40 100644 --- a/packages/local_auth/local_auth_android/test/local_auth_test.dart +++ b/packages/local_auth/local_auth_android/test/local_auth_test.dart @@ -24,9 +24,8 @@ void main() { channel.setMockMethodCallHandler((MethodCall methodCall) { log.add(methodCall); switch (methodCall.method) { - case 'getAvailableBiometrics': - return Future>.value( - ['face', 'fingerprint', 'iris', 'undefined']); + case 'getEnrolledBiometrics': + return Future>.value(['weak', 'strong']); default: return Future.value(true); } @@ -41,7 +40,7 @@ void main() { expect( log, [ - isMethodCall('getAvailableBiometrics', arguments: null), + isMethodCall('getEnrolledBiometrics', arguments: null), ], ); expect(result, true); @@ -54,13 +53,12 @@ void main() { expect( log, [ - isMethodCall('getAvailableBiometrics', arguments: null), + isMethodCall('getEnrolledBiometrics', arguments: null), ], ); expect(result, [ - BiometricType.face, - BiometricType.fingerprint, - BiometricType.iris + BiometricType.weak, + BiometricType.strong, ]); }); From 64f67764d0a1bf4878f3f58a630f436c2e81e57b Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 21 Apr 2022 17:47:23 +0200 Subject: [PATCH 2/9] Fix deviceSupportsBiometrics not returning true when biometrics are present but not enrolled. --- .../plugins/localauth/LocalAuthPlugin.java | 16 +++++++++------- .../lib/local_auth_android.dart | 4 +--- .../local_auth_android/test/local_auth_test.dart | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 258324e3c8c3..cecb04fb5472 100644 --- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -109,6 +109,9 @@ public void onMethodCall(MethodCall call, @NonNull final Result result) { case "stopAuthentication": stopAuthentication(result); break; + case "deviceSupportsBiometrics": + deviceSupportsBiometrics(result); + break; default: result.notImplemented(); break; @@ -247,8 +250,12 @@ private void stopAuthentication(Result result) { } } + private void deviceSupportsBiometrics(final Result result) { + result.success(hasBiometricHardware()); + } + /* - * Returns biometric types available on device + * Returns enrolled biometric types available on device. */ private void getEnrolledBiometrics(final Result result) { try { @@ -268,13 +275,8 @@ private ArrayList getEnrolledBiometrics() { if (activity == null || activity.isFinishing()) { return biometrics; } - // If no hardware is present, an empty list is returned. - if (!hasBiometricHardware()) { - return biometrics; - } - // If no biometrics are enrolled, only "undefined" is returned. + // If no hardware is present, or no biometrics are enrolled, an empty list is returned. if (!canAuthenticateWithBiometrics()) { - biometrics.add("undefined"); return biometrics; } // If there are biometrics enrolled, the available ones are returned. diff --git a/packages/local_auth/local_auth_android/lib/local_auth_android.dart b/packages/local_auth/local_auth_android/lib/local_auth_android.dart index 539ecc80f213..17d8c6b022c8 100644 --- a/packages/local_auth/local_auth_android/lib/local_auth_android.dart +++ b/packages/local_auth/local_auth_android/lib/local_auth_android.dart @@ -49,7 +49,7 @@ class LocalAuthAndroid extends LocalAuthPlatform { @override Future deviceSupportsBiometrics() async { - return (await getEnrolledBiometrics()).isNotEmpty; + return (await _channel.invokeMethod('deviceSupportsBiometrics')) ?? false; } @override @@ -67,8 +67,6 @@ class LocalAuthAndroid extends LocalAuthPlatform { case 'strong': biometrics.add(BiometricType.strong); break; - case 'undefined': - break; } } return biometrics; diff --git a/packages/local_auth/local_auth_android/test/local_auth_test.dart b/packages/local_auth/local_auth_android/test/local_auth_test.dart index d8f1e7ab1d40..55ac92626aea 100644 --- a/packages/local_auth/local_auth_android/test/local_auth_test.dart +++ b/packages/local_auth/local_auth_android/test/local_auth_test.dart @@ -34,13 +34,13 @@ void main() { log.clear(); }); - test('deviceSupportsBiometrics calls getEnrolledBiometrics', () async { + test('deviceSupportsBiometrics calls platform', () async { final bool result = await localAuthentication.deviceSupportsBiometrics(); expect( log, [ - isMethodCall('getEnrolledBiometrics', arguments: null), + isMethodCall('deviceSupportsBiometrics', arguments: null), ], ); expect(result, true); From 12d47c055f33bc29150a50e5f67dfffb1f422347 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 21 Apr 2022 17:48:33 +0200 Subject: [PATCH 3/9] Update changelog --- packages/local_auth/local_auth_android/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index b453cd40932e..45ef396ed70f 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -1,7 +1,8 @@ ## 1.0.1 -* Fix getEnrolledBiometrics to be in line with iOS implementation: +* Fixes `getEnrolledBiometrics` to match documented behaviour: Present biometrics that are not enrolled are no longer returned. +* `getEnrolledBiometrics` now only returns `weak` and `strong` biometric types. ## 1.0.0 From 001f17fa1161325cb818ff5266ae5ad1e36a637c Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 21 Apr 2022 17:49:07 +0200 Subject: [PATCH 4/9] Format --- .../java/io/flutter/plugins/localauth/LocalAuthPlugin.java | 2 +- .../local_auth/local_auth_android/lib/local_auth_android.dart | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index cecb04fb5472..f9fd7bdc7ed4 100644 --- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -251,7 +251,7 @@ private void stopAuthentication(Result result) { } private void deviceSupportsBiometrics(final Result result) { - result.success(hasBiometricHardware()); + result.success(hasBiometricHardware()); } /* diff --git a/packages/local_auth/local_auth_android/lib/local_auth_android.dart b/packages/local_auth/local_auth_android/lib/local_auth_android.dart index 17d8c6b022c8..2ce0f88477c7 100644 --- a/packages/local_auth/local_auth_android/lib/local_auth_android.dart +++ b/packages/local_auth/local_auth_android/lib/local_auth_android.dart @@ -49,7 +49,8 @@ class LocalAuthAndroid extends LocalAuthPlatform { @override Future deviceSupportsBiometrics() async { - return (await _channel.invokeMethod('deviceSupportsBiometrics')) ?? false; + return (await _channel.invokeMethod('deviceSupportsBiometrics')) ?? + false; } @override From deb9cef5efbe937cf42a6df67df73e55b4dbe878 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 21 Apr 2022 18:39:19 +0200 Subject: [PATCH 5/9] Fix native test --- .../io/flutter/plugins/localauth/LocalAuthTest.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java b/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java index 3f1fffc9cb75..8bc82c01446b 100644 --- a/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java +++ b/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java @@ -103,7 +103,7 @@ public void getEnrolledBiometrics_shouldReturnEmptyList_withoutHardwarePresent() } @Test - public void getEnrolledBiometrics_shouldReturnUndefined_withNoMethodsEnrolled() { + public void getEnrolledBiometrics_shouldReturnEmptyList_withNoMethodsEnrolled() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); setPluginActivity(plugin, buildMockActivity()); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); @@ -113,13 +113,7 @@ public void getEnrolledBiometrics_shouldReturnUndefined_withNoMethodsEnrolled() plugin.setBiometricManager(mockBiometricManager); plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); - verify(mockResult) - .success( - new ArrayList() { - { - add("undefined"); - } - }); + verify(mockResult).success(Collections.emptyList()); } @Test From 2daacc12ed6c46e6a68b655803af36ebe71d11f2 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 22 Apr 2022 00:30:44 +0200 Subject: [PATCH 6/9] Implement PR feedback --- .../local_auth_android/CHANGELOG.md | 1 + .../plugins/localauth/LocalAuthPlugin.java | 5 -- .../plugins/localauth/LocalAuthTest.java | 49 ++++++++++++++++++- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index 45ef396ed70f..c82ebd878e61 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -3,6 +3,7 @@ * Fixes `getEnrolledBiometrics` to match documented behaviour: Present biometrics that are not enrolled are no longer returned. * `getEnrolledBiometrics` now only returns `weak` and `strong` biometric types. +* `deviceSupportsBiometrics` now returns the correct value regardless of enrollment state. ## 1.0.0 diff --git a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index f9fd7bdc7ed4..3c5ecad16329 100644 --- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -275,11 +275,6 @@ private ArrayList getEnrolledBiometrics() { if (activity == null || activity.isFinishing()) { return biometrics; } - // If no hardware is present, or no biometrics are enrolled, an empty list is returned. - if (!canAuthenticateWithBiometrics()) { - return biometrics; - } - // If there are biometrics enrolled, the available ones are returned. if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS) { biometrics.add("weak"); diff --git a/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java b/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java index 8bc82c01446b..5fbda46b984f 100644 --- a/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java +++ b/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,6 +35,50 @@ public void isDeviceSupportedReturnsFalse() { verify(mockResult).success(false); } + @Test + public void deviceSupportsBiometrics_returnsTrueForPresentNonEnrolledBiometrics() { + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + final BiometricManager mockBiometricManager = mock(BiometricManager.class); + when(mockBiometricManager.canAuthenticate()) + .thenReturn(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED); + plugin.setBiometricManager(mockBiometricManager); + plugin.onMethodCall(new MethodCall("deviceSupportsBiometrics", null), mockResult); + verify(mockResult).success(true); + } + + @Test + public void deviceSupportsBiometrics_returnsTrueForPresentEnrolledBiometrics() { + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + final BiometricManager mockBiometricManager = mock(BiometricManager.class); + when(mockBiometricManager.canAuthenticate()).thenReturn(BiometricManager.BIOMETRIC_SUCCESS); + plugin.setBiometricManager(mockBiometricManager); + plugin.onMethodCall(new MethodCall("deviceSupportsBiometrics", null), mockResult); + verify(mockResult).success(true); + } + + @Test + public void deviceSupportsBiometrics_returnsFalseForNoBiometricHardware() { + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + final BiometricManager mockBiometricManager = mock(BiometricManager.class); + when(mockBiometricManager.canAuthenticate()) + .thenReturn(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE); + plugin.setBiometricManager(mockBiometricManager); + plugin.onMethodCall(new MethodCall("deviceSupportsBiometrics", null), mockResult); + verify(mockResult).success(false); + } + + @Test + public void deviceSupportsBiometrics_returnsFalseForNullBiometricManager() { + final LocalAuthPlugin plugin = new LocalAuthPlugin(); + final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + plugin.setBiometricManager(null); + plugin.onMethodCall(new MethodCall("deviceSupportsBiometrics", null), mockResult); + verify(mockResult).success(false); + } + @Test public void onDetachedFromActivity_ShouldReleaseActivity() { final Activity mockActivity = mock(Activity.class); @@ -94,7 +139,7 @@ public void getEnrolledBiometrics_shouldReturnEmptyList_withoutHardwarePresent() setPluginActivity(plugin, buildMockActivity()); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); final BiometricManager mockBiometricManager = mock(BiometricManager.class); - when(mockBiometricManager.canAuthenticate()) + when(mockBiometricManager.canAuthenticate(anyInt())) .thenReturn(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE); plugin.setBiometricManager(mockBiometricManager); @@ -108,7 +153,7 @@ public void getEnrolledBiometrics_shouldReturnEmptyList_withNoMethodsEnrolled() setPluginActivity(plugin, buildMockActivity()); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); final BiometricManager mockBiometricManager = mock(BiometricManager.class); - when(mockBiometricManager.canAuthenticate()) + when(mockBiometricManager.canAuthenticate(anyInt())) .thenReturn(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED); plugin.setBiometricManager(mockBiometricManager); From de7761e54d8b7916e4604aeb2d33e3022eb4500f Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 22 Apr 2022 00:34:38 +0200 Subject: [PATCH 7/9] Fix merge --- packages/local_auth/local_auth_android/CHANGELOG.md | 10 +++++++--- packages/local_auth/local_auth_android/pubspec.yaml | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index 72ff38294e79..33886fb287f3 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -1,11 +1,15 @@ -## 1.0.1 +## 1.0.2 -* Fixes `getEnrolledBiometrics` to match documented behaviour: +* Fixes `getEnrolledBiometrics` to match documented behaviour: Present biometrics that are not enrolled are no longer returned. -* `getEnrolledBiometrics` now only returns `weak` and `strong` biometric types. +* `getEnrolledBiometrics` now only returns `weak` and `strong` biometric types. * `deviceSupportsBiometrics` now returns the correct value regardless of enrollment state. * Adopts `Object.hash`. +## 1.0.1 + +* Adopts `Object.hash`. + ## 1.0.0 * Initial release from migration to federated architecture. diff --git a/packages/local_auth/local_auth_android/pubspec.yaml b/packages/local_auth/local_auth_android/pubspec.yaml index 5734843d6584..aad0b9b92e70 100644 --- a/packages/local_auth/local_auth_android/pubspec.yaml +++ b/packages/local_auth/local_auth_android/pubspec.yaml @@ -2,7 +2,7 @@ name: local_auth_android description: Android implementation of the local_auth plugin. repository: https://github.com/flutter/plugins/tree/master/packages/local_auth/local_auth_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 1.0.1 +version: 1.0.2 environment: sdk: ">=2.14.0 <3.0.0" From bced8de199001ba5175a03ac458427b2e5ea6a7f Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 22 Apr 2022 08:17:35 +0200 Subject: [PATCH 8/9] Remove duplicate line in changelog --- packages/local_auth/local_auth_android/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index 33886fb287f3..cbf686a2abcb 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -4,7 +4,6 @@ Present biometrics that are not enrolled are no longer returned. * `getEnrolledBiometrics` now only returns `weak` and `strong` biometric types. * `deviceSupportsBiometrics` now returns the correct value regardless of enrollment state. -* Adopts `Object.hash`. ## 1.0.1 From 87f1699c795be5aa49e54e953ad6858435b804f2 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 22 Apr 2022 09:03:22 +0200 Subject: [PATCH 9/9] Update example to use deviceSupportsBiometrics --- .../local_auth_android/example/lib/main.dart | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/local_auth/local_auth_android/example/lib/main.dart b/packages/local_auth/local_auth_android/example/lib/main.dart index 4c045214734d..29b1d66440eb 100644 --- a/packages/local_auth/local_auth_android/example/lib/main.dart +++ b/packages/local_auth/local_auth_android/example/lib/main.dart @@ -22,8 +22,8 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { _SupportState _supportState = _SupportState.unknown; - bool? _canCheckBiometrics; - List? _availableBiometrics; + bool? _deviceSupportsBiometrics; + List? _enrolledBiometrics; String _authorized = 'Not Authorized'; bool _isAuthenticating = false; @@ -38,12 +38,12 @@ class _MyAppState extends State { } Future _checkBiometrics() async { - late bool canCheckBiometrics; + late bool deviceSupportsBiometrics; try { - canCheckBiometrics = - (await LocalAuthPlatform.instance.getEnrolledBiometrics()).isNotEmpty; + deviceSupportsBiometrics = + await LocalAuthPlatform.instance.deviceSupportsBiometrics(); } on PlatformException catch (e) { - canCheckBiometrics = false; + deviceSupportsBiometrics = false; print(e); } if (!mounted) { @@ -51,7 +51,7 @@ class _MyAppState extends State { } setState(() { - _canCheckBiometrics = canCheckBiometrics; + _deviceSupportsBiometrics = deviceSupportsBiometrics; }); } @@ -69,7 +69,7 @@ class _MyAppState extends State { } setState(() { - _availableBiometrics = availableBiometrics; + _enrolledBiometrics = availableBiometrics; }); } @@ -171,15 +171,16 @@ class _MyAppState extends State { else const Text('This device is not supported'), const Divider(height: 100), - Text('Can check biometrics: $_canCheckBiometrics\n'), + Text( + 'Device supports biometrics: $_deviceSupportsBiometrics\n'), ElevatedButton( child: const Text('Check biometrics'), onPressed: _checkBiometrics, ), const Divider(height: 100), - Text('Available biometrics: $_availableBiometrics\n'), + Text('Enrolled biometrics: $_enrolledBiometrics\n'), ElevatedButton( - child: const Text('Get available biometrics'), + child: const Text('Get enrolled biometrics'), onPressed: _getEnrolledBiometrics, ), const Divider(height: 100),