0

I'm trying to create password manager autofilling login and password. Basing on Build autofill services I'm trying to begin with basic-level functionalities of app(providing autofill data inputs to other apps). I still have difficulities understanding how it works.

My project structure currently looks like:

First question: What should I implement in SettingsActivity, should it be LAUNCHER activity?

Second question: Am I getting it right that whenever you hit input field in any app, android calls onFillRequest() which we override in our app in class that inherits from AutofillService class, and fills it?

Beginning with Manifest file I added to 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" responds to service_configuration.xml file which looks like:

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

Here in official documenentation they suggest android:settingsActivity="SettingsActivity", and here comes my first question: What should I implement in SettingsActivity, should it be LAUNCHER activity? I've set android:settingsActivity to MainActivity, which is the only activity in my project. It's also blank right now:

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) {

    }
}

Going back to AndroidManifest.xml : android:name=".MyAutofillService" responds to MyAutofillService class in MyAutofillService.kt implemented basing on official documentation:

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()
    }
}

From what I understand, in above code we hardcode values of login and password data, which should be provided to other apps by our password manager:

// 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")

Current result: Service from app is showing on a list in autofill android settings: Autofill service android settings but after entering other apps and trying to login, none autofill data shows.

Second question: Am I getting it right that whenever you hit input field in any app, android calls onFillRequest() which we override in our app in class that inherits from AutofillService class, and fills it?

Adam
  • 1
  • 1

0 Answers0