如何使用原生Android创建Autofill Provider Service并将其集成到flutter应用程序中?

问题描述 投票:0回答:1

我有一个 flutter 移动应用程序,我需要实现自动填充提供程序服务,因此它可以用作密码管理器。 Flutter 尚未提供任何工具来实现此功能,因此我需要使用原生 Android(使用 Kotlin)(对于 Android 手机)和原生 iOS(使用 Swift)(对于 iOS 手机)来实现它。 从 android 开始,作为第一步,我在 flutter 和 android 之间架起了桥梁。 这是一些代码片段。 在我的应用程序的登录页面中:


static const channelKotlin = MethodChannel('com.myapp/channel');


 void _submit() async {
    await channelKotlin.invokeMethod('isAutoFillEnabled');
    // ....
  }

在android中我的代码结构是这样的:

android
    |__ app
         |__ src
             |__ main
                   |__kotlin
                           |__myapp
                                  |__ services__MyAutofillService.kt
                                  |__MainActivity.kt
        

其中MainActivity.kt文件是flutter生成的文件,经过编辑以完成flutter和android之间的桥梁,如下面的代码片段所示:


class MainActivity: FlutterFragmentActivity() {

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.myapp/channel").setMethodCallHandler {
            // This method is invoked on the main thread.
            call, result ->
            if(call.method == "isAutoFillEnabled") {
                var enabled =   MyFunctions.openAutoFillSetting(ContextWrapper(getApplicationContext()))
                println(enabled.toString())
            } else {
                result.notImplemented()
            }
        }
    }

}

对于MyAutofillService.kt文件,如果自动填充服务提供者按照官方文档构建自动填充服务来实现,如下所示:


const val TAG = "My Autofill"

class MyAutofillService : AutofillService() {

    data class ParsedStructure(var usernameId: AutofillId, var passwordId: AutofillId)

    data class UserData(var username: String, var password: String)

    private fun parseStructure(structure: AssistStructure) : ParsedStructure {
        println(structure.getActivityComponent())
        var viewNode = structure.getWindowNodeAt(0).getRootViewNode()
        return ParsedStructure(viewNode.getAutofillId()!!, viewNode.getAutofillId()!!)
    }

    private fun fetchUserData(structure: ParsedStructure): UserData {
        return UserData(structure.usernameId.toString(), structure.passwordId.toString())
    }

    private fun traverseStructure(structure: AssistStructure) {
        val windowNodes: List<AssistStructure.WindowNode> =
                structure.run {
                    (0 until windowNodeCount).map { getWindowNodeAt(it) }
                }

        windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
            val viewNode: ViewNode? = windowNode.rootViewNode
            traverseNode(viewNode)
        }
    }

    private fun traverseNode(viewNode: ViewNode?) {
        if (viewNode?.autofillHints?.isNotEmpty() == true) {
            // If the client app provides autofill hints, you can obtain them using
            //viewNode.getAutofillHints();
            println("1")
        } else {
            // Or use your own heuristics to describe the contents of a view
            // using methods such as getText() or getHint()
            println("2")
        }

        val children: List<ViewNode>? =
                viewNode?.run {
                    (0 until childCount).map { getChildAt(it) }
                }

        children?.forEach { childNode: ViewNode ->
            traverseNode(childNode)
        }
    }

    override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback) {
            Log.d(TAG, "onFillRequest()")

            //    Get the structure from the request
            val context: List<FillContext> = request.fillContexts
            val structure: AssistStructure = context[context.size - 1].structure

           // Traverse the structure looking for nodes to fill out
            val parsedStructure: ParsedStructure = parseStructure(structure)

           // Fetch user data that matches the fields
            val userData: UserData = fetchUserData(parsedStructure)

           // Build the presentation of the datasets
            val usernamePresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1)
            usernamePresentation.setTextViewText(android.R.id.text1, "my_username")
            val passwordPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1)
            passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username")

           // Add a dataset to the response
           // Builder object requires a non-null presentation
            val fillResponse: FillResponse = FillResponse.Builder()
                    .addDataset(
                            Dataset.Builder()
                                .setValue(
                                        parsedStructure.usernameId,
                                        AutofillValue.forText(userData.username),
                                        usernamePresentation
                                )
                                .setValue(
                                        parsedStructure.passwordId,
                                        AutofillValue.forText(userData.password),                                           passwordPresentation
                                )
                                .build()
                        )
                    .setSaveInfo(
                        SaveInfo.Builder(
                                SaveInfo.SAVE_DATA_TYPE_USERNAME or SaveInfo.SAVE_DATA_TYPE_PASSWORD,
                                arrayOf(parsedStructure.usernameId, parsedStructure.passwordId)
                    ).build()
                   )
            .build()

            println(fillResponse.toString())
            // If there are no errors, call onSuccess() and pass the response
            callback.onSuccess(fillResponse)

    }

    override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
        Log.d(TAG, "onSaveRequest()")

        // Get the structure from the request
        val context: List<FillContext> = request.fillContexts
        val structure: AssistStructure = context[context.size - 1].structure

        // Traverse the structure looking for data to save
        traverseStructure(structure)

        // Persist the data - if there are no errors, call onSuccess()
        callback.onSuccess()
    }

    override fun onCreate() {
        super.onCreate()
        // Perform any initialization tasks here
        Log.d(TAG, "onCreate")
    }

    override fun onConnected() {
        super.onConnected()
        Log.d(TAG, "onConnected")
    }

    override fun onDisconnected() {
        super.onDisconnected()
        Log.d(TAG, "onDisconnected")
    }

}

object MyFunctions{
    fun openAutoFillSetting(context: Context){
        val mAutoFillManager = context.getSystemService(AutofillManager::class.java)
        if (mAutoFillManager != null && mAutoFillManager.hasEnabledAutofillServices()) {
            Log.d(TAG, "Autofill service already enabled.")
        } else {
            Log.d(TAG, "Autofill service not yet enabled.")
            val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
            intent.setData(Uri.parse("package:myapp.services.MyAutofillService"))
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent)
        }
    }

}

MyAutofillService.kt 文件的上述逻辑只是文档中的代码,尚未在我的自定义实现中启动,它只是为了测试应用程序的交互以及是否正在显示数据。 还通过 AndroidManifest.xml 配置完成。

使用此代码,我可以弹出窗口以启用我的应用程序作为手机中的自动填充服务提供商,但是当使用另一个应用程序进行测试时,尝试填写表单,在服务中我得到有关表单正在填写的结果,并且onFillRequest 功能已启动,但我没有得到任何信息,或者当单击按钮登录并成功登录到此应用程序时,我没有收到请求保存凭据的弹出窗口。 虽然我最后得到了 fillResponse 变量,但我没有得到包含应用程序视图中的数据的下拉列表。下面是结果

println(fillResponse.toString()) 
onFillRequest函数末尾的行:

我无法找到问题出在哪里,如何实现自动填充服务提供商功能。

以及我如何在 iOS 上做到这一点。

预先感谢您的帮助。

android ios flutter autofill android-autofill-manager
1个回答
0
投票

问题解决了吗?我目前正在 kotlin 中研究类似的问题。

© www.soinside.com 2019 - 2024. All rights reserved.