我正在尝试创建密码管理器自动填充登录名和密码。 基于构建自动填充服务,我尝试从应用程序的基本功能开始(向其他应用程序提供自动填充数据输入)。我仍然很难理解它是如何工作的。
第一个问题:我应该在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 设置的列表中: 但进入其他应用程序并尝试登录后,没有显示自动填充数据。
第二个问题:我说得对吗,每当你在任何应用程序中点击输入字段时,Android 都会调用 onFillRequest() ,我们在继承自 AutofillService 类的应用程序中重写该方法并填充它?
@Adam 你想通了吗?我遇到了同样的问题,自动填充建议没有显示。请帮忙:)