android、kotlin - 如何创建自动填充服务?

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

我正在尝试创建密码管理器自动填充登录名和密码。 基于构建自动填充服务,我尝试从应用程序的基本功能开始(向其他应用程序提供自动填充数据输入)。我仍然很难理解它是如何工作的。

My project structure currently looks like:

第一个问题:我应该在SettingsActivity中实现什么,应该是LAUNCHER活动吗?

第二个问题:我说得对吗,每当你在任何应用程序中点击输入字段时,Android 都会调用 onFillRequest() ,我们在继承自 AutofillService 类的应用程序中重写该方法并填充它?

从我添加到 AndroidManifest.xml 的清单文件开始:

<service
            android:name=".MyAutofillService"
            android:exported="true"
            android:label="Custom Autofill Service"
            android:permission="android.permission.BIND_AUTOFILL_SERVICE">
            <intent-filter>
                <action android:name="android.service.autofill.AutofillService" />
            </intent-filter>
            <meta-data
                android:name="android.autofill"
                android:resource="@xml/service_configuration" />
        </service>

android:resource="@xml/service_configuration"
响应 service_configuration.xml 文件,如下所示:

<autofill-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsActivity="com.example.abc.MainActivity" />

在官方文档中,他们建议 android:settingsActivity="SettingsActivity",这是我的第一个问题:我应该在 SettingsActivity 中实现什么,它应该是 LAUNCHER 活动吗? 我已将 android:settingsActivity 设置为 MainActivity,这是我的项目中唯一的活动。现在也是空白:

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }

    fun saveEmailAddresses(binding: ActivityMainBinding) {

    }
}

回到AndroidManifest.xml:

android:name=".MyAutofillService"
响应基于官方文档实现的 MyAutofillService.kt 中的 MyAutofillService 类:

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: AssistStructure.ViewNode? = windowNode.rootViewNode
            traverseNode(viewNode)
        }
    }

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

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

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



    override fun onFillRequest(
        request: FillRequest,
        cancellationSignal: CancellationSignal,
        callback: FillCallback
    ) {
        // 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 (username: String, password: String) = fetchUserData(parsedStructure)

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

        // Add a dataset to the response
        val fillResponse: FillResponse = FillResponse.Builder()
            .addDataset(
                Dataset.Builder()
                .setValue(
                    parsedStructure.usernameId,
                    AutofillValue.forText(username),
                    usernamePresentation
                )
                .setValue(
                    parsedStructure.passwordId,
                    AutofillValue.forText(password),
                    passwordPresentation
                )
                .build())
            .build()

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

    override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
        // 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()
    }
}

据我了解,在上面的代码中,我们对登录和密码数据的值进行硬编码,这些值应由我们的密码管理器提供给其他应用程序:

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

当前结果: 应用程序的服务显示在自动填充 Android 设置的列表中: Autofill service android settings 但进入其他应用程序并尝试登录后,没有显示自动填充数据。

第二个问题:我说得对吗,每当你在任何应用程序中点击输入字段时,Android 都会调用 onFillRequest() ,我们在继承自 AutofillService 类的应用程序中重写该方法并填充它?

android kotlin autofill android-autofill-manager
1个回答
0
投票

@Adam 你想通了吗?我遇到了同样的问题,自动填充建议没有显示。请帮忙:)

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