summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Henneke <FabianHenneke@users.noreply.github.com>2020-03-25 17:53:58 +0100
committerGitHub <noreply@github.com>2020-03-25 17:53:58 +0100
commit5164b6951bbeaa025d92a0a5eb2f4c0250494988 (patch)
tree4657a621f8702df5697327829fe7dd50cfc2e455
parentfde8137b62a3d0cd8e4d94df6fde04f2f61cbbc9 (diff)
Add more lenient rules that apply only on manual request (#662)
Add rules that match password/username fields even if no heuristic matches, but only when the user explicitly requests Autofill. Since there is now a generic way to always trigger Autofill (at least in apps), other rules no longer need to match fields that fail the heuristics. Along the way, the apply functions in AutofillStrategy.kt are renamed to match in order to not conflict with the Kotlin apply() extension function. Furthermore, named parameters are used more widely now to pass around Booleans.
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillStrategy.kt25
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillStrategyDsl.kt38
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/Form.kt18
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt6
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt4
5 files changed, 74 insertions, 17 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillStrategy.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillStrategy.kt
index e1b157d5..07a38862 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillStrategy.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillStrategy.kt
@@ -88,8 +88,7 @@ val autofillStrategy = strategy {
breakTieOnPair { any { isFocused } }
}
username(optional = true) {
- takeSingle()
- breakTieOnSingle { usernameCertainty >= Likely }
+ takeSingle { usernameCertainty >= Likely }
breakTieOnSingle { usernameCertainty >= Certain }
breakTieOnSingle { alreadyMatched -> directlyPrecedes(alreadyMatched) }
breakTieOnSingle { isFocused }
@@ -104,8 +103,7 @@ val autofillStrategy = strategy {
breakTieOnSingle { isFocused }
}
username(optional = true) {
- takeSingle()
- breakTieOnSingle { usernameCertainty >= Likely }
+ takeSingle { usernameCertainty >= Likely }
breakTieOnSingle { usernameCertainty >= Certain }
breakTieOnSingle { alreadyMatched -> directlyPrecedes(alreadyMatched) }
breakTieOnSingle { isFocused }
@@ -176,4 +174,23 @@ val autofillStrategy = strategy {
breakTieOnSingle { hasAutocompleteHintUsername }
}
}
+
+ // Match any focused password field with optional username field on manual request.
+ rule(applyInSingleOriginMode = true, applyOnManualRequestOnly = true) {
+ genericPassword {
+ takeSingle { isFocused }
+ }
+ username(optional = true) {
+ takeSingle { alreadyMatched ->
+ usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull())
+ }
+ }
+ }
+
+ // Match any focused username field on manual request.
+ rule(applyInSingleOriginMode = true, applyOnManualRequestOnly = true) {
+ username {
+ takeSingle { isFocused }
+ }
+ }
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillStrategyDsl.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillStrategyDsl.kt
index 32ffaa2a..eea5c337 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillStrategyDsl.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillStrategyDsl.kt
@@ -149,6 +149,7 @@ private class PairOfFieldsMatcher(
class AutofillRule private constructor(
private val matchers: List<AutofillRuleMatcher>,
private val applyInSingleOriginMode: Boolean,
+ private val applyOnManualRequestOnly: Boolean,
private val name: String
) {
@@ -164,7 +165,10 @@ class AutofillRule private constructor(
}
@AutofillDsl
- class Builder(private val applyInSingleOriginMode: Boolean) {
+ class Builder(
+ private val applyInSingleOriginMode: Boolean,
+ private val applyOnManualRequestOnly: Boolean
+ ) {
companion object {
private var ruleId = 1
}
@@ -231,20 +235,25 @@ class AutofillRule private constructor(
require(matchers.none { it.matchHidden }) { "Rules with applyInSingleOriginMode set to true must not fill into hidden fields" }
}
return AutofillRule(
- matchers, applyInSingleOriginMode, name ?: "Rule #$ruleId"
+ matchers, applyInSingleOriginMode, applyOnManualRequestOnly, name ?: "Rule #$ruleId"
).also { ruleId++ }
}
}
- fun apply(
+ fun match(
allPassword: List<FormField>,
allUsername: List<FormField>,
- singleOriginMode: Boolean
+ singleOriginMode: Boolean,
+ isManualRequest: Boolean
): AutofillScenario<FormField>? {
if (singleOriginMode && !applyInSingleOriginMode) {
d { "$name: Skipped in single origin mode" }
return null
}
+ if (!isManualRequest && applyOnManualRequestOnly) {
+ d { "$name: Skipped since not a manual request" }
+ return null
+ }
d { "$name: Applying..." }
val scenarioBuilder = AutofillScenario.Builder<FormField>()
val alreadyMatched = mutableListOf<FormField>()
@@ -299,15 +308,25 @@ class AutofillStrategy private constructor(private val rules: List<AutofillRule>
fun rule(
applyInSingleOriginMode: Boolean = false,
+ applyOnManualRequestOnly: Boolean = false,
block: AutofillRule.Builder.() -> Unit
) {
- rules.add(AutofillRule.Builder(applyInSingleOriginMode).apply(block).build())
+ rules.add(
+ AutofillRule.Builder(
+ applyInSingleOriginMode = applyInSingleOriginMode,
+ applyOnManualRequestOnly = applyOnManualRequestOnly
+ ).apply(block).build()
+ )
}
fun build() = AutofillStrategy(rules)
}
- fun apply(fields: List<FormField>, multiOriginSupport: Boolean): AutofillScenario<FormField>? {
+ fun match(
+ fields: List<FormField>,
+ singleOriginMode: Boolean,
+ isManualRequest: Boolean
+ ): AutofillScenario<FormField>? {
val possiblePasswordFields =
fields.filter { it.passwordCertainty >= CertaintyLevel.Possible }
d { "Possible password fields: ${possiblePasswordFields.size}" }
@@ -317,7 +336,12 @@ class AutofillStrategy private constructor(private val rules: List<AutofillRule>
// Return the result of the first rule that matches
d { "Rules: ${rules.size}" }
for (rule in rules) {
- return rule.apply(possiblePasswordFields, possibleUsernameFields, multiOriginSupport)
+ return rule.match(
+ possiblePasswordFields,
+ possibleUsernameFields,
+ singleOriginMode = singleOriginMode,
+ isManualRequest = isManualRequest
+ )
?: continue
}
return null
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/Form.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/Form.kt
index 308391ec..ab21f00c 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/Form.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/Form.kt
@@ -69,7 +69,7 @@ sealed class FormOrigin(open val identifier: String) {
* Manages the detection of fields to fill in an [AssistStructure] and determines the [FormOrigin].
*/
@RequiresApi(Build.VERSION_CODES.O)
-private class Form(context: Context, structure: AssistStructure) {
+private class Form(context: Context, structure: AssistStructure, isManualRequest: Boolean) {
companion object {
private val SUPPORTED_SCHEMES = listOf("http", "https")
@@ -98,7 +98,7 @@ private class Form(context: Context, structure: AssistStructure) {
parseStructure(structure)
}
- val scenario = detectFieldsToFill()
+ val scenario = detectFieldsToFill(isManualRequest)
val formOrigin = determineFormOrigin(context)
init {
@@ -133,7 +133,11 @@ private class Form(context: Context, structure: AssistStructure) {
}
}
- private fun detectFieldsToFill() = autofillStrategy.apply(relevantFields, singleOriginMode)
+ private fun detectFieldsToFill(isManualRequest: Boolean) = autofillStrategy.match(
+ relevantFields,
+ singleOriginMode = singleOriginMode,
+ isManualRequest = isManualRequest
+ )
private fun trackOrigin(node: AssistStructure.ViewNode) {
if (!isTrustedBrowser) return
@@ -219,8 +223,12 @@ class FillableForm private constructor(
/**
* Returns a [FillableForm] if a login form could be detected in [structure].
*/
- fun parseAssistStructure(context: Context, structure: AssistStructure): FillableForm? {
- val form = Form(context, structure)
+ fun parseAssistStructure(
+ context: Context,
+ structure: AssistStructure,
+ isManualRequest: Boolean
+ ): FillableForm? {
+ val form = Form(context, structure, isManualRequest)
if (form.formOrigin == null || form.scenario == null) return null
return FillableForm(
form.formOrigin,
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt
index 00fa3aa4..350d187b 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt
@@ -18,6 +18,7 @@ import com.github.ajalt.timberkt.e
import com.zeapo.pwdstore.BuildConfig
import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSaveActivity
+import com.zeapo.pwdstore.utils.hasFlag
@RequiresApi(Build.VERSION_CODES.O)
class OreoAutofillService : AutofillService() {
@@ -62,7 +63,10 @@ class OreoAutofillService : AutofillService() {
}
return
}
- val formToFill = FillableForm.parseAssistStructure(this, structure) ?: run {
+ val formToFill = FillableForm.parseAssistStructure(
+ this, structure,
+ isManualRequest = request.flags hasFlag FillRequest.FLAG_MANUAL_REQUEST
+ ) ?: run {
d { "Form cannot be filled" }
callback.onSuccess(null)
return
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
index 68f8df27..f7a637c3 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
@@ -10,6 +10,10 @@ import android.util.TypedValue
import android.view.autofill.AutofillManager
import androidx.annotation.RequiresApi
+infix fun Int.hasFlag(flag: Int): Boolean {
+ return this and flag == flag
+}
+
fun String.splitLines(): Array<String> {
return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
}