Skip to content

Autofill Service to be able to give password hints directly and save passwords from autofill#162

Open
difanta wants to merge 17 commits intohegocre:mainfrom
difanta:main
Open

Autofill Service to be able to give password hints directly and save passwords from autofill#162
difanta wants to merge 17 commits intohegocre:mainfrom
difanta:main

Conversation

@difanta
Copy link

@difanta difanta commented Feb 12, 2026

This is a work in progress implementation of some features that I would consider very important for the Autofill Service.

  1. Let Autofill Service give hints right away instead of having to open the main app to complete the insertion
  2. Added AppLock activity that opens when the user selects an hint with the app lock enabled, the lock UI is shown and when completed the autofill request is completed. This uses Dataset.setAuthentication
  3. Support saving from the Autofill Service, this takes the first non null and non empty username and password, the searchHint as url and label and saves into new password or updates existing ones that match searchHint

This is still work in progress from an optimization perspective, and also I would like external opinion on a few choices.

  1. The Autofill Service on every Autofill Request syncs passwords with the Api, this is of course not ideal in a very limited time constrained service. If the app is rarely opened I find it necessary that the service syncs passwords, but maybe some other periodic background options are possible?
  2. Now the Service shows a list of hints of passwords matching searchHint, after those the original "NextcloudPasswords" hint that opens the app to select the password, and a "Generate Password" hint only if there are no passwords matching searchHint. This hint autofills a password already generated (according to the User preferences) while completing the FillRequest
  3. Saving works but needs more features, for example support for multiple-page logins, a dialog to choose the label of the password before saving (or maybe open the UI from the app to edit all the fields), support for inserting the master password if not saved

If anyone has the time to review this, I would much like to hear your thoughts, cheers.

… opening app; service opens app lock prompt if needed via Dataset.setAuthentication; autofill service can now save from autofill (wip)
@hegocre
Copy link
Owner

hegocre commented Feb 12, 2026

Hello,

Thank you very much for your work! I took a quick look at your code, and it looks nice!

To answer your questions:

  1. I think it would be great to do some form of rate limiting, specially because the autofill request method is called multiple times in the same autofill session (or at least, that's what I remember from when I created the service a while ago). Maybe, there could be a last sync parameter saved, and if it is less than let's say a couple of hours, the service does not sync.
  2. That's great for me!
  3. I like the idea of opening the edit password view, so the user can add some fields if he wants.

Thank you again!

…formation in intents, so no need for a separate lock only activity but rather main activity is in charge of authenticating and answering; fix building Save Info, now should work in most common cases, including delayed username and password insertion; wip: dedicated UI to handle save requests (create/update)
@hegocre
Copy link
Owner

hegocre commented Feb 17, 2026

Hello!

I have been able to install and try the app, and I have found two issues, to an otherwise amazing work!!

  • The first time fulfilling an autofill request (at least on a website), it only shows the nextcloud passwords and the generate options. When reloading, the options appear fine.
  • The autofill suggestions don't seem to follow the Strict domain matching setting.

Thanks again for your contribution!

Best regards.

only support saving from version P;
@difanta
Copy link
Author

difanta commented Feb 21, 2026

Hi! I finally managed to get some work done, I have fixed the issue of the first autofill request not showing the correct passwords, it did not wait correctly for the decryption and so they were only ready by the second request.

I don't know what might cause the fact that strict domain matching is not followed, I've found a small modification to make in order to make the code identical to NCPNavHost but I would have to investigate further if this is not sufficient.

In any case a few updates:

Now the autofill service runs only with cached keychain and passwords, this is the only way to make it fast enough to be usable. A deferred update must be the way to sync passwords periodically.

The autofill response is always handled by the main app, and specifically a new interface (AutofillData) is passed down by the components of the app until NCPNavHost handles them. There are 4 possible cases
1 "FromId" - A password was clicked in the autofill completion -> After passwords are decrypted directly answer the autofill with the password with that id. This case is directly done by the AutofillService, not by the main app, if and only if there is no app lock or it is already unlocked
2 "ChoosePwd" - The user clicked the button to choose the password in the app, usual behaviour of the old autofill
3 "Save" - The user chose to save (or update) a new (or existing) password -> the password list is filtered for that domain, the user is free to create or edit passwords on the list. When opening one it automatically inserts the new label, url, username and password. Then the user saves the new or updated password(s) and must do a back gesture to exit the passwords app
4 "SaveAutofill" - Same as Save but triggered only when clicking on the "Create new password" hint, the user is free to create one or more passwords and eventually will click "Save and autofill" on one of them (the autofill does not go through due to a bug I'm investigating but this is basically done)

I'm missing a translation, which I don't know how to do and, if you agree, an icon to replace the big "Nextcloud Passwords" hint to something smaller such as ">" or any icon that fits the idea. Look for TODO if you'd like to spot these.

@difanta difanta marked this pull request as ready for review February 22, 2026 20:34
@difanta
Copy link
Author

difanta commented Feb 22, 2026

This should be ready, the create password bug is solved.
Strict domain matching might not be possible beacuse the only domain that the autofill service can read from the structure is AssistStructure.ViewNode#getWebDomain(), and this does not output the full url.
The only open things are the translation of "Create new password" and the decision about the icon/text to use for the "open main app" button.

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC
org.gradle.java.home=C:\\Program Files\\Java\\latest\\jdk-21
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this shouldn't be here? It produces a build error in systems where the jdk is not at this exact path

Copy link
Author

@difanta difanta Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I messed up a commit with things i kept local, should be good now

Copy link
Owner

@hegocre hegocre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great!

builder.addDataset(
AutofillHelper.buildDataset(
applicationContext,
PasswordAutofillData(label = ">", id = null, username = null, password = null), // TODO use icon
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I would change the > Label with a "More". I think it looks weird otherwise.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, done

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && useInline) {
request.inlineSuggestionsRequest
} else null
// TODO: when to sync with server?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this done? Should we do it before merging?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes for now, the sync is done when opening the main app (even to complete/authenticate an autofill) so it is a minor problem in my opinion.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements significant enhancements to the Nextcloud Passwords Android app's Autofill Service, enabling direct password hints, app lock integration, and save functionality. The changes transform the autofill experience from requiring the main app to be opened for every autofill request to providing inline password suggestions directly in the autofill UI.

Changes:

  • Introduced AutofillData sealed class to represent different autofill operation types (FromId, ChoosePwd, SaveAutofill, Save)
  • Refactored autofill service to provide inline password suggestions with app lock authentication support
  • Added save password functionality that captures credentials and allows saving/updating passwords from autofill contexts

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 28 comments.

Show a summary per file
File Description
AutofillUtils.kt New utility file defining data classes for autofill operations
PasswordEditView.kt Changed parameter from boolean flag to AutofillData object for more structured autofill handling
NCPTopBar.kt Updated to accept AutofillData instead of boolean for autofill state
NCPNavHost.kt Major refactoring to handle different autofill scenarios with branching logic based on AutofillData type
NCPApp.kt Updated to pass AutofillData through component hierarchy
MainActivity.kt Modified to extract and process AutofillData from intents
NCPAutofillService.kt Complete rewrite with async processing, password decryption flow, and inline dataset generation
AutofillHelper.kt Enhanced to build datasets with authentication support and save info
AssistStructureParser.kt Improved field detection with content extraction for save functionality
build.gradle Added kotlin-parcelize plugin for Parcelable support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

structure,
null,
intentSender
PasswordAutofillData(label = "Create new password", id = null, username = null, password = null), // TODO: translation
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded user-facing text without translation. The text "Create new password" should be externalized to a string resource for internationalization. Replace with a reference to a string resource using context.getString(R.string.create_new_password) or similar.

Copilot uses AI. Check for mistakes.
)
Log.d(TAG, "Passwords filtered and sorted, count: ${filteredList.size}, needs input for master password: $needsAppForMasterPassword")

val needsAuth = hasAppLock.first() && (isLocked.firstOrNull() ?: true)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The needsAuth flag is computed but there's potential for a race condition or stale data. The isLocked flow is accessed with firstOrNull() which could return null if the flow hasn't emitted yet, defaulting to true (locked). However, this creates uncertainty - if the flow state is unknown, should the system default to locked or unlocked? Consider using a more explicit initial state or add a timeout with clear error handling.

Suggested change
val needsAuth = hasAppLock.first() && (isLocked.firstOrNull() ?: true)
val hasLockEnabled = hasAppLock.first()
val needsAuth = if (!hasLockEnabled) {
false
} else {
val lockedState = withTimeoutOrNull(1000L) {
isLocked.first()
}
if (lockedState == null) {
Log.w(TAG, "Timed out waiting for isLocked state; defaulting to locked for autofill")
true
} else {
lockedState
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +501 to +505
label = if(label.isNullOrBlank()) saveData.label else label
url = if(url.isNullOrBlank()) saveData.url else url
// prioritize new username and password fields
username = if(saveData.username.isNullOrBlank()) username else saveData.username
password = if(saveData.password.isNullOrBlank()) password else saveData.password
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent null safety checks. The code checks for null and blank separately in lines 501-505, but uses isNullOrBlank() elsewhere in the file. For consistency and clarity, use the isNullOrBlank() extension function instead of separate null and blank checks.

Copilot uses AI. Check for mistakes.
@hegocre
Copy link
Owner

hegocre commented Feb 26, 2026

I have been using this version to test, and I have seen that I get suggestions for fields that are not username/password, such as the twitter or reddit search bar on the browser.

@difanta
Copy link
Author

difanta commented Feb 28, 2026

Looked trough the AI comments and fixed some concerns, Passwords decrypt lazily to save battery, the (password) state management of NavHost was reverted to what it was before.

Regarding your comment that this autofills other textual fields, before this PR the autofill was done only if there were >0 password fields. Now to support username only fields this is no longer true, and so I disabled the autofill of textual fields. In the case where there are no username fields, the last textual field before the password is still used.
Can you check if this solved the issue? I do not remember which sites had this issue for me so I cannot really check for sure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants