如何使用原生Android创建自动填充提供程序服务并将其集成到flutter应用程序中?

7d7tgy0s  于 2023-08-07  发布在  Flutter
关注(0)|答案(1)|浏览(100)

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

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

个字符
在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文件,如果自动填充服务提供商遵循官方文档Build autofill services,则实现如下:

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变量,但我没有得到包含应用程序视图中数据的下拉列表。下面是onFillRequest函数末尾println(fillResponse.toString())行的结果:


的数据
我无法找到哪里可能是问题,我如何才能实现自动填充服务提供程序功能。
那么,我该如何为iOS做到这一点呢?
提前感谢您的帮助。

ki1q1bka

ki1q1bka1#

问题解决了吗?我目前正在Kotlin中处理类似的问题。

相关问题