diff --git a/packages/local_auth/local_auth/pubspec.yaml b/packages/local_auth/local_auth/pubspec.yaml index dbffb4cda879..baf5d46b7b00 100644 --- a/packages/local_auth/local_auth/pubspec.yaml +++ b/packages/local_auth/local_auth/pubspec.yaml @@ -17,8 +17,7 @@ flutter: plugin: platforms: android: - package: io.flutter.plugins.localauth - pluginClass: LocalAuthPlugin + default_package: local_auth_android ios: default_package: local_auth_ios @@ -27,7 +26,9 @@ dependencies: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 intl: ^0.17.0 - # Temporary path dependencies to allow moving Android and iOS implementations. +# Temporary path dependencies to allow moving Android and iOS implementations. + local_auth_android: + path: ../local_auth_android local_auth_ios: path: ../local_auth_ios local_auth_platform_interface: ^1.0.1 diff --git a/packages/local_auth/local_auth_android/AUTHORS b/packages/local_auth/local_auth_android/AUTHORS new file mode 100644 index 000000000000..d5694690c247 --- /dev/null +++ b/packages/local_auth/local_auth_android/AUTHORS @@ -0,0 +1,67 @@ +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. +The Chromium Authors +German Saprykin +Benjamin Sauer +larsenthomasj@gmail.com +Ali Bitek +Pol Batlló +Anatoly Pulyaevskiy +Hayden Flinner +Stefano Rodriguez +Salvatore Giordano +Brian Armstrong +Paul DeMarco +Fabricio Nogueira +Simon Lightfoot +Ashton Thomas +Thomas Danner +Diego Velásquez +Hajime Nakamura +Tuyển Vũ Xuân +Miguel Ruivo +Sarthak Verma +Mike Diarmid +Invertase +Elliot Hesp +Vince Varga +Aawaz Gyawali +EUI Limited +Katarina Sheremet +Thomas Stockx +Sarbagya Dhaubanjar +Ozkan Eksi +Rishab Nayak +ko2ic +Jonathan Younger +Jose Sanchez +Debkanchan Samadder +Audrius Karosevicius +Lukasz Piliszczuk +SoundReply Solutions GmbH +Rafal Wachol +Pau Picas +Christian Weder +Alexandru Tuca +Christian Weder +Rhodes Davis Jr. +Luigi Agosti +Quentin Le Guennec +Koushik Ravikumar +Nissim Dsilva +Giancarlo Rocha +Ryo Miyake +Théo Champion +Kazuki Yamaguchi +Eitan Schwartz +Chris Rutkowski +Juan Alvarez +Aleksandr Yurkovskiy +Anton Borries +Alex Li +Rahul Raj <64.rahulraj@gmail.com> +Bodhi Mulders diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md new file mode 100644 index 000000000000..7f198f2d66c0 --- /dev/null +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +* Initial release from migration to federated architecture. diff --git a/packages/local_auth/local_auth_android/LICENSE b/packages/local_auth/local_auth_android/LICENSE new file mode 100644 index 000000000000..c6823b81eb84 --- /dev/null +++ b/packages/local_auth/local_auth_android/LICENSE @@ -0,0 +1,25 @@ +Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/local_auth/local_auth_android/README.md b/packages/local_auth/local_auth_android/README.md new file mode 100644 index 000000000000..07244912f231 --- /dev/null +++ b/packages/local_auth/local_auth_android/README.md @@ -0,0 +1,11 @@ +# local\_auth\_android + +The Android implementation of [`local_auth`][1]. + +## Usage + +This package is [endorsed][2], which means you can simply use `local_auth` +normally. This package will be automatically included in your app when you do. + +[1]: https://pub.dev/packages/local_auth +[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin \ No newline at end of file diff --git a/packages/local_auth/local_auth/android/build.gradle b/packages/local_auth/local_auth_android/android/build.gradle similarity index 100% rename from packages/local_auth/local_auth/android/build.gradle rename to packages/local_auth/local_auth_android/android/build.gradle diff --git a/packages/local_auth/local_auth/android/lint-baseline.xml b/packages/local_auth/local_auth_android/android/lint-baseline.xml similarity index 100% rename from packages/local_auth/local_auth/android/lint-baseline.xml rename to packages/local_auth/local_auth_android/android/lint-baseline.xml diff --git a/packages/local_auth/local_auth/android/settings.gradle b/packages/local_auth/local_auth_android/android/settings.gradle similarity index 100% rename from packages/local_auth/local_auth/android/settings.gradle rename to packages/local_auth/local_auth_android/android/settings.gradle diff --git a/packages/local_auth/local_auth/android/src/main/AndroidManifest.xml b/packages/local_auth/local_auth_android/android/src/main/AndroidManifest.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/AndroidManifest.xml rename to packages/local_auth/local_auth_android/android/src/main/AndroidManifest.xml diff --git a/packages/local_auth/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java similarity index 100% rename from packages/local_auth/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java rename to packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java diff --git a/packages/local_auth/local_auth/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 similarity index 99% rename from packages/local_auth/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java rename to packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index a63e22a512d0..49a6b788fe46 100644 --- a/packages/local_auth/local_auth/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 @@ -40,7 +40,7 @@ */ @SuppressWarnings("deprecation") public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware { - private static final String CHANNEL_NAME = "plugins.flutter.io/local_auth"; + private static final String CHANNEL_NAME = "plugins.flutter.io/local_auth_android"; private static final int LOCK_REQUEST_CODE = 221; private Activity activity; private final AtomicBoolean authInProgress = new AtomicBoolean(false); diff --git a/packages/local_auth/local_auth/android/src/main/res/drawable/fingerprint_initial_icon.xml b/packages/local_auth/local_auth_android/android/src/main/res/drawable/fingerprint_initial_icon.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/drawable/fingerprint_initial_icon.xml rename to packages/local_auth/local_auth_android/android/src/main/res/drawable/fingerprint_initial_icon.xml diff --git a/packages/local_auth/local_auth/android/src/main/res/drawable/fingerprint_success_icon.xml b/packages/local_auth/local_auth_android/android/src/main/res/drawable/fingerprint_success_icon.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/drawable/fingerprint_success_icon.xml rename to packages/local_auth/local_auth_android/android/src/main/res/drawable/fingerprint_success_icon.xml diff --git a/packages/local_auth/local_auth/android/src/main/res/drawable/fingerprint_warning_icon.xml b/packages/local_auth/local_auth_android/android/src/main/res/drawable/fingerprint_warning_icon.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/drawable/fingerprint_warning_icon.xml rename to packages/local_auth/local_auth_android/android/src/main/res/drawable/fingerprint_warning_icon.xml diff --git a/packages/local_auth/local_auth/android/src/main/res/drawable/ic_done_white_24dp.xml b/packages/local_auth/local_auth_android/android/src/main/res/drawable/ic_done_white_24dp.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/drawable/ic_done_white_24dp.xml rename to packages/local_auth/local_auth_android/android/src/main/res/drawable/ic_done_white_24dp.xml diff --git a/packages/local_auth/local_auth/android/src/main/res/drawable/ic_fingerprint_white_24dp.xml b/packages/local_auth/local_auth_android/android/src/main/res/drawable/ic_fingerprint_white_24dp.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/drawable/ic_fingerprint_white_24dp.xml rename to packages/local_auth/local_auth_android/android/src/main/res/drawable/ic_fingerprint_white_24dp.xml diff --git a/packages/local_auth/local_auth/android/src/main/res/drawable/ic_priority_high_white_24dp.xml b/packages/local_auth/local_auth_android/android/src/main/res/drawable/ic_priority_high_white_24dp.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/drawable/ic_priority_high_white_24dp.xml rename to packages/local_auth/local_auth_android/android/src/main/res/drawable/ic_priority_high_white_24dp.xml diff --git a/packages/local_auth/local_auth/android/src/main/res/layout/go_to_setting.xml b/packages/local_auth/local_auth_android/android/src/main/res/layout/go_to_setting.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/layout/go_to_setting.xml rename to packages/local_auth/local_auth_android/android/src/main/res/layout/go_to_setting.xml diff --git a/packages/local_auth/local_auth/android/src/main/res/layout/scan_fp.xml b/packages/local_auth/local_auth_android/android/src/main/res/layout/scan_fp.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/layout/scan_fp.xml rename to packages/local_auth/local_auth_android/android/src/main/res/layout/scan_fp.xml diff --git a/packages/local_auth/local_auth/android/src/main/res/values/colors.xml b/packages/local_auth/local_auth_android/android/src/main/res/values/colors.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/values/colors.xml rename to packages/local_auth/local_auth_android/android/src/main/res/values/colors.xml diff --git a/packages/local_auth/local_auth/android/src/main/res/values/dimens.xml b/packages/local_auth/local_auth_android/android/src/main/res/values/dimens.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/values/dimens.xml rename to packages/local_auth/local_auth_android/android/src/main/res/values/dimens.xml diff --git a/packages/local_auth/local_auth/android/src/main/res/values/styles.xml b/packages/local_auth/local_auth_android/android/src/main/res/values/styles.xml similarity index 100% rename from packages/local_auth/local_auth/android/src/main/res/values/styles.xml rename to packages/local_auth/local_auth_android/android/src/main/res/values/styles.xml diff --git a/packages/local_auth/local_auth/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 similarity index 100% rename from packages/local_auth/local_auth/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java rename to packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java diff --git a/packages/local_auth/local_auth_android/example/README.md b/packages/local_auth/local_auth_android/example/README.md new file mode 100644 index 000000000000..a4a6091c9ba6 --- /dev/null +++ b/packages/local_auth/local_auth_android/example/README.md @@ -0,0 +1,8 @@ +# local_auth_example + +Demonstrates how to use the local_auth plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](https://flutter.dev/). diff --git a/packages/local_auth/local_auth_android/example/android/app/build.gradle b/packages/local_auth/local_auth_android/example/android/app/build.gradle new file mode 100644 index 000000000000..d1cef4bf53a9 --- /dev/null +++ b/packages/local_auth/local_auth_android/example/android/app/build.gradle @@ -0,0 +1,58 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 31 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "io.flutter.plugins.localauthexample" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} diff --git a/packages/local_auth/local_auth_android/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/local_auth/local_auth_android/example/android/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..186b71557c50 --- /dev/null +++ b/packages/local_auth/local_auth_android/example/android/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java new file mode 100644 index 000000000000..0f4298dca155 --- /dev/null +++ b/packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -0,0 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DartIntegrationTest {} diff --git a/packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/localauth/FlutterFragmentActivityTest.java b/packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/localauth/FlutterFragmentActivityTest.java new file mode 100644 index 000000000000..68c22371d7dd --- /dev/null +++ b/packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/localauth/FlutterFragmentActivityTest.java @@ -0,0 +1,20 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.localauth; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.integration_test.FlutterTestRunner; +import io.flutter.embedding.android.FlutterFragmentActivity; +import io.flutter.plugins.DartIntegrationTest; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@DartIntegrationTest +@RunWith(FlutterTestRunner.class) +public class FlutterFragmentActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(FlutterFragmentActivity.class); +} diff --git a/packages/local_auth/local_auth_android/example/android/app/src/main/AndroidManifest.xml b/packages/local_auth/local_auth_android/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..8c091772107a --- /dev/null +++ b/packages/local_auth/local_auth_android/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000000..db77bb4b7b09 Binary files /dev/null and b/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000000..17987b79bb8a Binary files /dev/null and b/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000000..09d4391482be Binary files /dev/null and b/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000000..d5f1c8d34e7a Binary files /dev/null and b/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000000..4d6372eebdb2 Binary files /dev/null and b/packages/local_auth/local_auth_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/local_auth/local_auth_android/example/android/build.gradle b/packages/local_auth/local_auth_android/example/android/build.gradle new file mode 100644 index 000000000000..54c943621de5 --- /dev/null +++ b/packages/local_auth/local_auth_android/example/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.1' + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/local_auth/local_auth_android/example/android/gradle.properties b/packages/local_auth/local_auth_android/example/android/gradle.properties new file mode 100644 index 000000000000..7fe61a74cee0 --- /dev/null +++ b/packages/local_auth/local_auth_android/example/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1024m +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/packages/local_auth/local_auth_android/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/local_auth/local_auth_android/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..cd9fe1c68282 --- /dev/null +++ b/packages/local_auth/local_auth_android/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#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-6.5-all.zip diff --git a/packages/local_auth/local_auth_android/example/android/settings.gradle b/packages/local_auth/local_auth_android/example/android/settings.gradle new file mode 100644 index 000000000000..115da6cb4f4d --- /dev/null +++ b/packages/local_auth/local_auth_android/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withInputStream { stream -> plugins.load(stream) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/packages/local_auth/local_auth_android/example/android/settings_aar.gradle b/packages/local_auth/local_auth_android/example/android/settings_aar.gradle new file mode 100644 index 000000000000..e7b4def49cb5 --- /dev/null +++ b/packages/local_auth/local_auth_android/example/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/packages/local_auth/local_auth_android/example/integration_test/local_auth_test.dart b/packages/local_auth/local_auth_android/example/integration_test/local_auth_test.dart new file mode 100644 index 000000000000..1dfc0ae7a6d6 --- /dev/null +++ b/packages/local_auth/local_auth_android/example/integration_test/local_auth_test.dart @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'package:local_auth_android/local_auth_android.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('canCheckBiometrics', (WidgetTester tester) async { + expect( + LocalAuthAndroid().getEnrolledBiometrics(), + completion(isList), + ); + }); +} diff --git a/packages/local_auth/local_auth_android/example/lib/main.dart b/packages/local_auth/local_auth_android/example/lib/main.dart new file mode 100644 index 000000000000..4c045214734d --- /dev/null +++ b/packages/local_auth/local_auth_android/example/lib/main.dart @@ -0,0 +1,238 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:local_auth_android/local_auth_android.dart'; +import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + _SupportState _supportState = _SupportState.unknown; + bool? _canCheckBiometrics; + List? _availableBiometrics; + String _authorized = 'Not Authorized'; + bool _isAuthenticating = false; + + @override + void initState() { + super.initState(); + LocalAuthPlatform.instance.isDeviceSupported().then( + (bool isSupported) => setState(() => _supportState = isSupported + ? _SupportState.supported + : _SupportState.unsupported), + ); + } + + Future _checkBiometrics() async { + late bool canCheckBiometrics; + try { + canCheckBiometrics = + (await LocalAuthPlatform.instance.getEnrolledBiometrics()).isNotEmpty; + } on PlatformException catch (e) { + canCheckBiometrics = false; + print(e); + } + if (!mounted) { + return; + } + + setState(() { + _canCheckBiometrics = canCheckBiometrics; + }); + } + + Future _getEnrolledBiometrics() async { + late List availableBiometrics; + try { + availableBiometrics = + await LocalAuthPlatform.instance.getEnrolledBiometrics(); + } on PlatformException catch (e) { + availableBiometrics = []; + print(e); + } + if (!mounted) { + return; + } + + setState(() { + _availableBiometrics = availableBiometrics; + }); + } + + Future _authenticate() async { + bool authenticated = false; + try { + setState(() { + _isAuthenticating = true; + _authorized = 'Authenticating'; + }); + authenticated = await LocalAuthPlatform.instance.authenticate( + localizedReason: 'Let OS determine authentication method', + authMessages: [const AndroidAuthMessages()], + options: const AuthenticationOptions( + useErrorDialogs: true, + stickyAuth: true, + ), + ); + setState(() { + _isAuthenticating = false; + }); + } on PlatformException catch (e) { + print(e); + setState(() { + _isAuthenticating = false; + _authorized = 'Error - ${e.message}'; + }); + return; + } + if (!mounted) { + return; + } + + setState( + () => _authorized = authenticated ? 'Authorized' : 'Not Authorized'); + } + + Future _authenticateWithBiometrics() async { + bool authenticated = false; + try { + setState(() { + _isAuthenticating = true; + _authorized = 'Authenticating'; + }); + authenticated = await LocalAuthPlatform.instance.authenticate( + localizedReason: + 'Scan your fingerprint (or face or whatever) to authenticate', + authMessages: [const AndroidAuthMessages()], + options: const AuthenticationOptions( + useErrorDialogs: true, + stickyAuth: true, + biometricOnly: true, + ), + ); + setState(() { + _isAuthenticating = false; + _authorized = 'Authenticating'; + }); + } on PlatformException catch (e) { + print(e); + setState(() { + _isAuthenticating = false; + _authorized = 'Error - ${e.message}'; + }); + return; + } + if (!mounted) { + return; + } + + final String message = authenticated ? 'Authorized' : 'Not Authorized'; + setState(() { + _authorized = message; + }); + } + + Future _cancelAuthentication() async { + await LocalAuthPlatform.instance.stopAuthentication(); + setState(() => _isAuthenticating = false); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: ListView( + padding: const EdgeInsets.only(top: 30), + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (_supportState == _SupportState.unknown) + const CircularProgressIndicator() + else if (_supportState == _SupportState.supported) + const Text('This device is supported') + else + const Text('This device is not supported'), + const Divider(height: 100), + Text('Can check biometrics: $_canCheckBiometrics\n'), + ElevatedButton( + child: const Text('Check biometrics'), + onPressed: _checkBiometrics, + ), + const Divider(height: 100), + Text('Available biometrics: $_availableBiometrics\n'), + ElevatedButton( + child: const Text('Get available biometrics'), + onPressed: _getEnrolledBiometrics, + ), + const Divider(height: 100), + Text('Current State: $_authorized\n'), + if (_isAuthenticating) + ElevatedButton( + onPressed: _cancelAuthentication, + child: Row( + mainAxisSize: MainAxisSize.min, + children: const [ + Text('Cancel Authentication'), + Icon(Icons.cancel), + ], + ), + ) + else + Column( + children: [ + ElevatedButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: const [ + Text('Authenticate'), + Icon(Icons.perm_device_information), + ], + ), + onPressed: _authenticate, + ), + ElevatedButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(_isAuthenticating + ? 'Cancel' + : 'Authenticate: biometrics only'), + const Icon(Icons.fingerprint), + ], + ), + onPressed: _authenticateWithBiometrics, + ), + ], + ), + ], + ), + ], + ), + ), + ); + } +} + +enum _SupportState { + unknown, + supported, + unsupported, +} diff --git a/packages/local_auth/local_auth_android/example/pubspec.yaml b/packages/local_auth/local_auth_android/example/pubspec.yaml new file mode 100644 index 000000000000..c07a81d2be3b --- /dev/null +++ b/packages/local_auth/local_auth_android/example/pubspec.yaml @@ -0,0 +1,28 @@ +name: local_auth_android_example +description: Demonstrates how to use the local_auth_android plugin. +publish_to: none + +environment: + sdk: ">=2.14.0 <3.0.0" + flutter: ">=2.8.0" + +dependencies: + flutter: + sdk: flutter + local_auth_android: + # When depending on this package from a real application you should use: + # local_auth_android: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + local_auth_platform_interface: ^1.0.0 + +dev_dependencies: + flutter_driver: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true diff --git a/packages/local_auth/local_auth_android/example/test_driver/integration_test.dart b/packages/local_auth/local_auth_android/example/test_driver/integration_test.dart new file mode 100644 index 000000000000..4f10f2a522f3 --- /dev/null +++ b/packages/local_auth/local_auth_android/example/test_driver/integration_test.dart @@ -0,0 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); 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 new file mode 100644 index 000000000000..a3f314e3347b --- /dev/null +++ b/packages/local_auth/local_auth_android/lib/local_auth_android.dart @@ -0,0 +1,87 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; +import 'package:local_auth_android/types/auth_messages_android.dart'; +import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; +import 'package:local_auth_platform_interface/types/auth_messages.dart'; +import 'package:local_auth_platform_interface/types/auth_options.dart'; +import 'package:local_auth_platform_interface/types/biometric_type.dart'; + +export 'package:local_auth_android/types/auth_messages_android.dart'; +export 'package:local_auth_platform_interface/types/auth_messages.dart'; +export 'package:local_auth_platform_interface/types/auth_options.dart'; +export 'package:local_auth_platform_interface/types/biometric_type.dart'; + +const MethodChannel _channel = + MethodChannel('plugins.flutter.io/local_auth_android'); + +/// The implementation of [LocalAuthPlatform] for Android. +class LocalAuthAndroid extends LocalAuthPlatform { + /// Registers this class as the default instance of [LocalAuthPlatform]. + static void registerWith() { + LocalAuthPlatform.instance = LocalAuthAndroid(); + } + + @override + Future authenticate({ + required String localizedReason, + required Iterable authMessages, + AuthenticationOptions options = const AuthenticationOptions(), + }) async { + assert(localizedReason.isNotEmpty); + final Map args = { + 'localizedReason': localizedReason, + 'useErrorDialogs': options.useErrorDialogs, + 'stickyAuth': options.stickyAuth, + 'sensitiveTransaction': options.sensitiveTransaction, + 'biometricOnly': options.biometricOnly, + }; + args.addAll(const AndroidAuthMessages().args); + for (final AuthMessages messages in authMessages) { + if (messages is AndroidAuthMessages) { + args.addAll(messages.args); + } + } + return (await _channel.invokeMethod('authenticate', args)) ?? false; + } + + @override + Future deviceSupportsBiometrics() async { + return (await getEnrolledBiometrics()).isNotEmpty; + } + + @override + Future> getEnrolledBiometrics() async { + final List result = (await _channel.invokeListMethod( + 'getAvailableBiometrics', + )) ?? + []; + final List biometrics = []; + for (final String value in result) { + switch (value) { + case 'face': + biometrics.add(BiometricType.face); + break; + case 'fingerprint': + biometrics.add(BiometricType.fingerprint); + break; + case 'iris': + biometrics.add(BiometricType.iris); + break; + case 'undefined': + break; + } + } + return biometrics; + } + + @override + Future isDeviceSupported() async => + (await _channel.invokeMethod('isDeviceSupported')) ?? false; + + @override + Future stopAuthentication() async => + await _channel.invokeMethod('stopAuthentication') ?? false; +} diff --git a/packages/local_auth/local_auth_android/lib/types/auth_messages_android.dart b/packages/local_auth/local_auth_android/lib/types/auth_messages_android.dart new file mode 100644 index 000000000000..ea61a4b06d4e --- /dev/null +++ b/packages/local_auth/local_auth_android/lib/types/auth_messages_android.dart @@ -0,0 +1,191 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart'; +import 'package:local_auth_platform_interface/types/auth_messages.dart'; + +/// Android side authentication messages. +/// +/// Provides default values for all messages. +@immutable +class AndroidAuthMessages extends AuthMessages { + /// Constructs a new instance. + const AndroidAuthMessages({ + this.biometricHint, + this.biometricNotRecognized, + this.biometricRequiredTitle, + this.biometricSuccess, + this.cancelButton, + this.deviceCredentialsRequiredTitle, + this.deviceCredentialsSetupDescription, + this.goToSettingsButton, + this.goToSettingsDescription, + this.signInTitle, + }); + + /// Hint message advising the user how to authenticate with biometrics. + /// Maximum 60 characters. + final String? biometricHint; + + /// Message to let the user know that authentication was failed. + /// Maximum 60 characters. + final String? biometricNotRecognized; + + /// Message shown as a title in a dialog which indicates the user + /// has not set up biometric authentication on their device. + /// Maximum 60 characters. + final String? biometricRequiredTitle; + + /// Message to let the user know that authentication was successful. + /// Maximum 60 characters + final String? biometricSuccess; + + /// Message shown on a button that the user can click to leave the + /// current dialog. + /// Maximum 30 characters. + final String? cancelButton; + + /// Message shown as a title in a dialog which indicates the user + /// has not set up credentials authentication on their device. + /// Maximum 60 characters. + final String? deviceCredentialsRequiredTitle; + + /// Message advising the user to go to the settings and configure + /// device credentials on their device. + final String? deviceCredentialsSetupDescription; + + /// Message shown on a button that the user can click to go to settings pages + /// from the current dialog. + /// Maximum 30 characters. + final String? goToSettingsButton; + + /// Message advising the user to go to the settings and configure + /// biometric on their device. + final String? goToSettingsDescription; + + /// Message shown as a title in a dialog which indicates the user + /// that they need to scan biometric to continue. + /// Maximum 60 characters. + final String? signInTitle; + + @override + Map get args { + return { + 'biometricHint': biometricHint ?? androidBiometricHint, + 'biometricNotRecognized': + biometricNotRecognized ?? androidBiometricNotRecognized, + 'biometricSuccess': biometricSuccess ?? androidBiometricSuccess, + 'biometricRequired': + biometricRequiredTitle ?? androidBiometricRequiredTitle, + 'cancelButton': cancelButton ?? androidCancelButton, + 'deviceCredentialsRequired': deviceCredentialsRequiredTitle ?? + androidDeviceCredentialsRequiredTitle, + 'deviceCredentialsSetupDescription': deviceCredentialsSetupDescription ?? + androidDeviceCredentialsSetupDescription, + 'goToSetting': goToSettingsButton ?? goToSettings, + 'goToSettingDescription': + goToSettingsDescription ?? androidGoToSettingsDescription, + 'signInTitle': signInTitle ?? androidSignInTitle, + }; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AndroidAuthMessages && + runtimeType == other.runtimeType && + biometricHint == other.biometricHint && + biometricNotRecognized == other.biometricNotRecognized && + biometricRequiredTitle == other.biometricRequiredTitle && + biometricSuccess == other.biometricSuccess && + cancelButton == other.cancelButton && + deviceCredentialsRequiredTitle == + other.deviceCredentialsRequiredTitle && + deviceCredentialsSetupDescription == + other.deviceCredentialsSetupDescription && + goToSettingsButton == other.goToSettingsButton && + goToSettingsDescription == other.goToSettingsDescription && + signInTitle == other.signInTitle; + + @override + int get hashCode => + biometricHint.hashCode ^ + biometricNotRecognized.hashCode ^ + biometricRequiredTitle.hashCode ^ + biometricSuccess.hashCode ^ + cancelButton.hashCode ^ + deviceCredentialsRequiredTitle.hashCode ^ + deviceCredentialsSetupDescription.hashCode ^ + goToSettingsButton.hashCode ^ + goToSettingsDescription.hashCode ^ + signInTitle.hashCode; +} + +// Default strings for AndroidAuthMessages. Currently supports English. +// Intl.message must be string literals. + +/// Message shown on a button that the user can click to go to settings pages +/// from the current dialog. +String get goToSettings => Intl.message('Go to settings', + desc: 'Message shown on a button that the user can click to go to ' + 'settings pages from the current dialog. Maximum 30 characters.'); + +/// Hint message advising the user how to authenticate with biometrics. +String get androidBiometricHint => Intl.message('Verify identity', + desc: 'Hint message advising the user how to authenticate with biometrics. ' + 'Maximum 60 characters.'); + +/// Message to let the user know that authentication was failed. +String get androidBiometricNotRecognized => + Intl.message('Not recognized. Try again.', + desc: 'Message to let the user know that authentication was failed. ' + 'Maximum 60 characters.'); + +/// Message to let the user know that authentication was successful. It +String get androidBiometricSuccess => Intl.message('Success', + desc: 'Message to let the user know that authentication was successful. ' + 'Maximum 60 characters.'); + +/// Message shown on a button that the user can click to leave the +/// current dialog. +String get androidCancelButton => Intl.message('Cancel', + desc: 'Message shown on a button that the user can click to leave the ' + 'current dialog. Maximum 30 characters.'); + +/// Message shown as a title in a dialog which indicates the user +/// that they need to scan biometric to continue. +String get androidSignInTitle => Intl.message('Authentication required', + desc: 'Message shown as a title in a dialog which indicates the user ' + 'that they need to scan biometric to continue. Maximum 60 characters.'); + +/// Message shown as a title in a dialog which indicates the user +/// has not set up biometric authentication on their device. +String get androidBiometricRequiredTitle => Intl.message('Biometric required', + desc: 'Message shown as a title in a dialog which indicates the user ' + 'has not set up biometric authentication on their device. ' + 'Maximum 60 characters.'); + +/// Message shown as a title in a dialog which indicates the user +/// has not set up credentials authentication on their device. +String get androidDeviceCredentialsRequiredTitle => + Intl.message('Device credentials required', + desc: 'Message shown as a title in a dialog which indicates the user ' + 'has not set up credentials authentication on their device. ' + 'Maximum 60 characters.'); + +/// Message advising the user to go to the settings and configure +/// device credentials on their device. +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.'); + +/// Message advising the user to go to the settings and configure +/// biometric on their device. +String get androidGoToSettingsDescription => Intl.message( + '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 ' + 'biometric on their device.'); diff --git a/packages/local_auth/local_auth_android/pubspec.yaml b/packages/local_auth/local_auth_android/pubspec.yaml new file mode 100644 index 000000000000..ec2991db6a90 --- /dev/null +++ b/packages/local_auth/local_auth_android/pubspec.yaml @@ -0,0 +1,29 @@ +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 + +environment: + sdk: ">=2.14.0 <3.0.0" + flutter: ">=2.8.0" + +flutter: + plugin: + implements: local_auth + platforms: + android: + package: io.flutter.plugins.localauth + pluginClass: LocalAuthPlugin + dartPluginClass: LocalAuthAndroid + +dependencies: + flutter: + sdk: flutter + flutter_plugin_android_lifecycle: ^2.0.1 + intl: ^0.17.0 + local_auth_platform_interface: ^1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter \ No newline at end of file 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 new file mode 100644 index 000000000000..31f5e5796445 --- /dev/null +++ b/packages/local_auth/local_auth_android/test/local_auth_test.dart @@ -0,0 +1,181 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:local_auth_android/local_auth_android.dart'; +import 'package:local_auth_platform_interface/types/auth_messages.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('LocalAuth', () { + const MethodChannel channel = MethodChannel( + 'plugins.flutter.io/local_auth_android', + ); + + final List log = []; + late LocalAuthAndroid localAuthentication; + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) { + log.add(methodCall); + switch (methodCall.method) { + case 'getAvailableBiometrics': + return Future>.value( + ['face', 'fingerprint', 'iris', 'undefined']); + default: + return Future.value(true); + } + }); + localAuthentication = LocalAuthAndroid(); + log.clear(); + }); + + test('deviceSupportsBiometrics calls getEnrolledBiometrics', () async { + final bool result = await localAuthentication.deviceSupportsBiometrics(); + + expect( + log, + [ + isMethodCall('getAvailableBiometrics', arguments: null), + ], + ); + expect(result, true); + }); + + test('getEnrolledBiometrics calls platform', () async { + final List result = + await localAuthentication.getEnrolledBiometrics(); + + expect( + log, + [ + isMethodCall('getAvailableBiometrics', arguments: null), + ], + ); + expect(result, [ + BiometricType.face, + BiometricType.fingerprint, + BiometricType.iris + ]); + }); + + test('isDeviceSupported calls platform', () async { + await localAuthentication.isDeviceSupported(); + expect( + log, + [ + isMethodCall('isDeviceSupported', arguments: null), + ], + ); + }); + + test('stopAuthentication calls platform', () async { + await localAuthentication.stopAuthentication(); + expect( + log, + [ + isMethodCall('stopAuthentication', arguments: null), + ], + ); + }); + + group('With device auth fail over', () { + test('authenticate with no args.', () async { + await localAuthentication.authenticate( + authMessages: [const AndroidAuthMessages()], + localizedReason: 'Needs secure', + options: const AuthenticationOptions(biometricOnly: true), + ); + expect( + log, + [ + isMethodCall('authenticate', + arguments: { + 'localizedReason': 'Needs secure', + 'useErrorDialogs': true, + 'stickyAuth': false, + 'sensitiveTransaction': true, + 'biometricOnly': true, + }..addAll(const AndroidAuthMessages().args)), + ], + ); + }); + + test('authenticate with no sensitive transaction.', () async { + await localAuthentication.authenticate( + authMessages: [const AndroidAuthMessages()], + localizedReason: 'Insecure', + options: const AuthenticationOptions( + sensitiveTransaction: false, + useErrorDialogs: false, + biometricOnly: true, + ), + ); + expect( + log, + [ + isMethodCall('authenticate', + arguments: { + 'localizedReason': 'Insecure', + 'useErrorDialogs': false, + 'stickyAuth': false, + 'sensitiveTransaction': false, + 'biometricOnly': true, + }..addAll(const AndroidAuthMessages().args)), + ], + ); + }); + }); + + group('With biometrics only', () { + test('authenticate with no args.', () async { + await localAuthentication.authenticate( + authMessages: [const AndroidAuthMessages()], + localizedReason: 'Needs secure', + ); + expect( + log, + [ + isMethodCall('authenticate', + arguments: { + 'localizedReason': 'Needs secure', + 'useErrorDialogs': true, + 'stickyAuth': false, + 'sensitiveTransaction': true, + 'biometricOnly': false, + }..addAll(const AndroidAuthMessages().args)), + ], + ); + }); + + test('authenticate with no sensitive transaction.', () async { + await localAuthentication.authenticate( + authMessages: [const AndroidAuthMessages()], + localizedReason: 'Insecure', + options: const AuthenticationOptions( + sensitiveTransaction: false, + useErrorDialogs: false, + ), + ); + expect( + log, + [ + isMethodCall('authenticate', + arguments: { + 'localizedReason': 'Insecure', + 'useErrorDialogs': false, + 'stickyAuth': false, + 'sensitiveTransaction': false, + 'biometricOnly': false, + }..addAll(const AndroidAuthMessages().args)), + ], + ); + }); + }); + }); +}