From af583aa54a40532221d3c1d3eff8040011645aa7 Mon Sep 17 00:00:00 2001 From: Long Yang Paffrath Date: Thu, 13 Feb 2025 18:59:31 +0100 Subject: [PATCH] Keep full authentication proedure on the same task stack for better ux on android --- .../AuthenticationManagementActivity.kt | 85 +++++++++++++++++++ .../flutter_web_auth_2/CallbackActivity.kt | 3 +- .../FlutterWebAuth2Plugin.kt | 42 ++++++--- .../android/app/src/main/AndroidManifest.xml | 2 + flutter_web_auth_2/lib/src/options.dart | 7 +- 5 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/AuthenticationManagementActivity.kt diff --git a/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/AuthenticationManagementActivity.kt b/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/AuthenticationManagementActivity.kt new file mode 100644 index 0000000..2fd7463 --- /dev/null +++ b/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/AuthenticationManagementActivity.kt @@ -0,0 +1,85 @@ +package com.linusu.flutter_web_auth_2 + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.browser.customtabs.CustomTabsIntent + +class AuthenticationManagementActivity( +) : Activity() { + companion object { + const val KEY_AUTH_STARTED: String = "authStarted" + const val KEY_AUTH_URI: String = "authUri" + const val KEY_AUTH_OPTION_INTENT_FLAGS: String = "authOptionsIntentFlags" + const val KEY_AUTH_OPTION_TARGET_PACKAGE: String = "authOptionsTargetPackage" + + fun createResponseHandlingIntent(context: Context): Intent { + val intent = Intent(context, AuthenticationManagementActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + return intent + } + } + private var authStarted: Boolean = false + private lateinit var authenticationUri: Uri + private var intentFlags: Int = 0 + private var targetPackage: String? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (savedInstanceState == null) { + extractState(intent.extras) + } else { + extractState(savedInstanceState) + } + } + + override fun onResume() { + super.onResume() + + if (!authStarted) { + val intent = CustomTabsIntent.Builder().build() + intent.intent.addFlags(intentFlags) + + if (targetPackage != null) { + intent.intent.setPackage(targetPackage) + } + intent.launchUrl(this, authenticationUri) + authStarted = true + return + } + /* If the authentication was already started and we've returned here, the user either + * completed or cancelled authentication. + * Either way we want to return to our original flutter activity, so just finish here + */ + finish() + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + setIntent(intent); + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putBoolean(KEY_AUTH_STARTED, authStarted) + outState.putParcelable(KEY_AUTH_URI, authenticationUri) + outState.putInt(KEY_AUTH_OPTION_INTENT_FLAGS, intentFlags) + outState.putString( + KEY_AUTH_OPTION_TARGET_PACKAGE, + targetPackage + ) + } + + private fun extractState(state: Bundle?) { + if (state == null) { + finish() + return + } + authStarted = state.getBoolean(KEY_AUTH_STARTED, false) + authenticationUri = state.getParcelable(KEY_AUTH_URI)!! + intentFlags = state.getInt(KEY_AUTH_OPTION_INTENT_FLAGS, 0) + targetPackage = state.getString(KEY_AUTH_OPTION_TARGET_PACKAGE) + } +} \ No newline at end of file diff --git a/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/CallbackActivity.kt b/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/CallbackActivity.kt index c165396..85eeb16 100644 --- a/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/CallbackActivity.kt +++ b/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/CallbackActivity.kt @@ -18,7 +18,8 @@ class CallbackActivity : Activity() { if (scheme != null) { FlutterWebAuth2Plugin.callbacks.remove(scheme)?.success(url.toString()) } - finishAndRemoveTask() + startActivity(AuthenticationManagementActivity.createResponseHandlingIntent(this)) + finish() } diff --git a/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/FlutterWebAuth2Plugin.kt b/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/FlutterWebAuth2Plugin.kt index e8d62be..6f97e49 100644 --- a/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/FlutterWebAuth2Plugin.kt +++ b/flutter_web_auth_2/android/src/main/kotlin/com/linusu/flutter_web_auth_2/FlutterWebAuth2Plugin.kt @@ -1,5 +1,6 @@ package com.linusu.flutter_web_auth_2 +import android.app.Activity import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -9,16 +10,20 @@ import androidx.browser.customtabs.CustomTabsClient import androidx.browser.customtabs.CustomTabsIntent import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result +import java.util.ArrayList class FlutterWebAuth2Plugin( private var context: Context? = null, - private var channel: MethodChannel? = null -) : MethodCallHandler, FlutterPlugin { + private var channel: MethodChannel? = null, + private var activity: Activity? = null, +) : MethodCallHandler, FlutterPlugin, ActivityAware { companion object { val callbacks = mutableMapOf() } @@ -46,17 +51,11 @@ class FlutterWebAuth2Plugin( val options = call.argument>("options")!! callbacks[callbackUrlScheme] = resultCallback - val intent = CustomTabsIntent.Builder().build() - val keepAliveIntent = Intent(context, KeepAliveService::class.java) - - intent.intent.addFlags(options["intentFlags"] as Int) - intent.intent.putExtra("android.support.customtabs.extra.KEEP_ALIVE", keepAliveIntent) - - val targetPackage = findTargetBrowserPackageName(options) - if (targetPackage != null) { - intent.intent.setPackage(targetPackage) - } - intent.launchUrl(context!!, url) + activity?.startActivity(Intent(activity,AuthenticationManagementActivity::class.java).apply { + putExtra(AuthenticationManagementActivity.KEY_AUTH_URI,url) + putExtra(AuthenticationManagementActivity.KEY_AUTH_OPTION_INTENT_FLAGS, options["intentFlags"] as Int) + putExtra(AuthenticationManagementActivity.KEY_AUTH_OPTION_TARGET_PACKAGE, findTargetBrowserPackageName(options)) + }) } "cleanUpDanglingCalls" -> { @@ -71,6 +70,22 @@ class FlutterWebAuth2Plugin( } } + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + } + + override fun onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity() + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + onAttachedToActivity(binding) + } + + override fun onDetachedFromActivity() { + activity = null + } + /** * Find Support CustomTabs Browser. * @@ -145,5 +160,4 @@ class FlutterWebAuth2Plugin( ) return value == packageName } - } diff --git a/flutter_web_auth_2/example/android/app/src/main/AndroidManifest.xml b/flutter_web_auth_2/example/android/app/src/main/AndroidManifest.xml index f30be49..4b93b6a 100644 --- a/flutter_web_auth_2/example/android/app/src/main/AndroidManifest.xml +++ b/flutter_web_auth_2/example/android/app/src/main/AndroidManifest.xml @@ -49,6 +49,8 @@ + +