fix(deps): update dependency com.android.tools.build:gradle to v8.3.0-alpha05 (#2692)
* fix(deps): update dependency com.android.tools.build:gradle to v8.3.0-alpha05 * refactor(autofill): workaround `RequiresApi` Lint --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
079be4a810
commit
da17f0b4fb
6 changed files with 237 additions and 204 deletions
|
@ -0,0 +1,26 @@
|
||||||
|
package app.passwordstore.injection
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import app.passwordstore.util.autofill.Api26AutofillResponseBuilder
|
||||||
|
import app.passwordstore.util.autofill.Api30AutofillResponseBuilder
|
||||||
|
import app.passwordstore.util.autofill.AutofillResponseBuilder
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.Reusable
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object AutofillResponseBuilderModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Reusable
|
||||||
|
fun provideAutofillResponseBuilder(): AutofillResponseBuilder.Factory {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
Api30AutofillResponseBuilder.Factory
|
||||||
|
} else {
|
||||||
|
Api26AutofillResponseBuilder.Factory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
package app.passwordstore.util.autofill
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.IntentSender
|
||||||
|
import android.os.Build
|
||||||
|
import android.service.autofill.Dataset
|
||||||
|
import android.service.autofill.FillCallback
|
||||||
|
import android.service.autofill.FillRequest
|
||||||
|
import android.service.autofill.FillResponse
|
||||||
|
import android.service.autofill.SaveInfo
|
||||||
|
import app.passwordstore.autofill.oreo.ui.AutofillSmsActivity
|
||||||
|
import app.passwordstore.ui.autofill.AutofillDecryptActivity
|
||||||
|
import app.passwordstore.ui.autofill.AutofillFilterView
|
||||||
|
import app.passwordstore.ui.autofill.AutofillPublisherChangedActivity
|
||||||
|
import app.passwordstore.ui.autofill.AutofillSaveActivity
|
||||||
|
import com.github.androidpasswordstore.autofillparser.AutofillAction
|
||||||
|
import com.github.androidpasswordstore.autofillparser.FillableForm
|
||||||
|
import com.github.androidpasswordstore.autofillparser.fillWith
|
||||||
|
import com.github.michaelbull.result.fold
|
||||||
|
import java.io.File
|
||||||
|
import logcat.LogPriority.ERROR
|
||||||
|
import logcat.asLog
|
||||||
|
import logcat.logcat
|
||||||
|
|
||||||
|
class Api26AutofillResponseBuilder
|
||||||
|
private constructor(
|
||||||
|
form: FillableForm,
|
||||||
|
) : AutofillResponseBuilder {
|
||||||
|
|
||||||
|
object Factory : AutofillResponseBuilder.Factory {
|
||||||
|
override fun create(
|
||||||
|
form: FillableForm,
|
||||||
|
): AutofillResponseBuilder = Api26AutofillResponseBuilder(form)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val formOrigin = form.formOrigin
|
||||||
|
private val scenario = form.scenario
|
||||||
|
private val ignoredIds = form.ignoredIds
|
||||||
|
private val saveFlags = form.saveFlags
|
||||||
|
private val clientState = form.toClientState()
|
||||||
|
|
||||||
|
// We do not offer save when the only relevant field is a username field or there is no field.
|
||||||
|
private val scenarioSupportsSave = scenario.hasPasswordFieldsToSave
|
||||||
|
private val canBeSaved = saveFlags != null && scenarioSupportsSave
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
private fun makeIntentDataset(
|
||||||
|
context: Context,
|
||||||
|
action: AutofillAction,
|
||||||
|
intentSender: IntentSender,
|
||||||
|
metadata: DatasetMetadata,
|
||||||
|
): Dataset {
|
||||||
|
return Dataset.Builder(makeRemoteView(context, metadata)).run {
|
||||||
|
fillWith(scenario, action, credentials = null)
|
||||||
|
setAuthentication(intentSender)
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeMatchDataset(context: Context, file: File): Dataset? {
|
||||||
|
if (!scenario.hasFieldsToFillOn(AutofillAction.Match)) return null
|
||||||
|
val metadata = makeFillMatchMetadata(context, file)
|
||||||
|
val intentSender = AutofillDecryptActivity.makeDecryptFileIntentSender(file, context)
|
||||||
|
return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeSearchDataset(context: Context): Dataset? {
|
||||||
|
if (!scenario.hasFieldsToFillOn(AutofillAction.Search)) return null
|
||||||
|
val metadata = makeSearchAndFillMetadata(context)
|
||||||
|
val intentSender = AutofillFilterView.makeMatchAndDecryptFileIntentSender(context, formOrigin)
|
||||||
|
return makeIntentDataset(context, AutofillAction.Search, intentSender, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeGenerateDataset(context: Context): Dataset? {
|
||||||
|
if (!scenario.hasFieldsToFillOn(AutofillAction.Generate)) return null
|
||||||
|
val metadata = makeGenerateAndFillMetadata(context)
|
||||||
|
val intentSender = AutofillSaveActivity.makeSaveIntentSender(context, null, formOrigin)
|
||||||
|
return makeIntentDataset(context, AutofillAction.Generate, intentSender, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeFillOtpFromSmsDataset(context: Context): Dataset? {
|
||||||
|
if (!scenario.hasFieldsToFillOn(AutofillAction.FillOtpFromSms)) return null
|
||||||
|
if (!AutofillSmsActivity.shouldOfferFillFromSms(context)) return null
|
||||||
|
val metadata = makeFillOtpFromSmsMetadata(context)
|
||||||
|
val intentSender = AutofillSmsActivity.makeFillOtpFromSmsIntentSender(context)
|
||||||
|
return makeIntentDataset(context, AutofillAction.FillOtpFromSms, intentSender, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makePublisherChangedDataset(
|
||||||
|
context: Context,
|
||||||
|
publisherChangedException: AutofillPublisherChangedException,
|
||||||
|
): Dataset {
|
||||||
|
val metadata = makeWarningMetadata(context)
|
||||||
|
// If the user decides to trust the new publisher, they can choose reset the list of
|
||||||
|
// matches. In this case we need to immediately show a new `FillResponse` as if the app were
|
||||||
|
// autofilled for the first time. This `FillResponse` needs to be returned as a result from
|
||||||
|
// `AutofillPublisherChangedActivity`, which is why we create and pass it on here.
|
||||||
|
val fillResponseAfterReset = makeFillResponse(context, emptyList())
|
||||||
|
val intentSender =
|
||||||
|
AutofillPublisherChangedActivity.makePublisherChangedIntentSender(
|
||||||
|
context,
|
||||||
|
publisherChangedException,
|
||||||
|
fillResponseAfterReset
|
||||||
|
)
|
||||||
|
return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makePublisherChangedResponse(
|
||||||
|
context: Context,
|
||||||
|
publisherChangedException: AutofillPublisherChangedException
|
||||||
|
): FillResponse {
|
||||||
|
return FillResponse.Builder().run {
|
||||||
|
addDataset(makePublisherChangedDataset(context, publisherChangedException))
|
||||||
|
setIgnoredIds(*ignoredIds.toTypedArray())
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support multi-step authentication flows in apps via FLAG_DELAY_SAVE
|
||||||
|
// See:
|
||||||
|
// https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE
|
||||||
|
private fun makeSaveInfo(): SaveInfo? {
|
||||||
|
if (!canBeSaved) return null
|
||||||
|
check(saveFlags != null)
|
||||||
|
val idsToSave = scenario.fieldsToSave.toTypedArray()
|
||||||
|
if (idsToSave.isEmpty()) return null
|
||||||
|
var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD
|
||||||
|
if (scenario.hasUsername) {
|
||||||
|
saveDataTypes = saveDataTypes or SaveInfo.SAVE_DATA_TYPE_USERNAME
|
||||||
|
}
|
||||||
|
return SaveInfo.Builder(saveDataTypes, idsToSave).run {
|
||||||
|
setFlags(saveFlags)
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeFillResponse(context: Context, matchedFiles: List<File>): FillResponse? {
|
||||||
|
var datasetCount = 0
|
||||||
|
return FillResponse.Builder().run {
|
||||||
|
for (file in matchedFiles) {
|
||||||
|
makeMatchDataset(context, file)?.let {
|
||||||
|
datasetCount++
|
||||||
|
addDataset(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
makeGenerateDataset(context)?.let {
|
||||||
|
datasetCount++
|
||||||
|
addDataset(it)
|
||||||
|
}
|
||||||
|
makeFillOtpFromSmsDataset(context)?.let {
|
||||||
|
datasetCount++
|
||||||
|
addDataset(it)
|
||||||
|
}
|
||||||
|
makeSearchDataset(context)?.let {
|
||||||
|
datasetCount++
|
||||||
|
addDataset(it)
|
||||||
|
}
|
||||||
|
if (datasetCount == 0) return null
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
setHeader(
|
||||||
|
makeRemoteView(
|
||||||
|
context,
|
||||||
|
makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
makeSaveInfo()?.let { setSaveInfo(it) }
|
||||||
|
setClientState(clientState)
|
||||||
|
setIgnoredIds(*ignoredIds.toTypedArray())
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates and returns a suitable [FillResponse] to the Autofill framework. */
|
||||||
|
override fun fillCredentials(context: Context, fillRequest: FillRequest, callback: FillCallback) {
|
||||||
|
AutofillMatcher.getMatchesFor(context, formOrigin)
|
||||||
|
.fold(
|
||||||
|
success = { matchedFiles -> callback.onSuccess(makeFillResponse(context, matchedFiles)) },
|
||||||
|
failure = { e ->
|
||||||
|
logcat(ERROR) { e.asLog() }
|
||||||
|
callback.onSuccess(makePublisherChangedResponse(context, e))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import android.content.IntentSender
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.service.autofill.Dataset
|
import android.service.autofill.Dataset
|
||||||
import android.service.autofill.FillCallback
|
import android.service.autofill.FillCallback
|
||||||
|
import android.service.autofill.FillRequest
|
||||||
import android.service.autofill.FillResponse
|
import android.service.autofill.FillResponse
|
||||||
import android.service.autofill.Presentations
|
import android.service.autofill.Presentations
|
||||||
import android.service.autofill.SaveInfo
|
import android.service.autofill.SaveInfo
|
||||||
|
@ -25,9 +26,6 @@ import com.github.androidpasswordstore.autofillparser.AutofillAction
|
||||||
import com.github.androidpasswordstore.autofillparser.FillableForm
|
import com.github.androidpasswordstore.autofillparser.FillableForm
|
||||||
import com.github.androidpasswordstore.autofillparser.fillWith
|
import com.github.androidpasswordstore.autofillparser.fillWith
|
||||||
import com.github.michaelbull.result.fold
|
import com.github.michaelbull.result.fold
|
||||||
import dagger.assisted.Assisted
|
|
||||||
import dagger.assisted.AssistedFactory
|
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import logcat.LogPriority.ERROR
|
import logcat.LogPriority.ERROR
|
||||||
import logcat.asLog
|
import logcat.asLog
|
||||||
|
@ -36,14 +34,14 @@ import logcat.logcat
|
||||||
/** Implements [AutofillResponseBuilder]'s methods for API 30 and above */
|
/** Implements [AutofillResponseBuilder]'s methods for API 30 and above */
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
class Api30AutofillResponseBuilder
|
class Api30AutofillResponseBuilder
|
||||||
@AssistedInject
|
private constructor(
|
||||||
constructor(
|
form: FillableForm,
|
||||||
@Assisted form: FillableForm,
|
) : AutofillResponseBuilder {
|
||||||
) {
|
|
||||||
|
|
||||||
@AssistedFactory
|
object Factory : AutofillResponseBuilder.Factory {
|
||||||
interface Factory {
|
override fun create(
|
||||||
fun create(form: FillableForm): Api30AutofillResponseBuilder
|
form: FillableForm,
|
||||||
|
): AutofillResponseBuilder = Api30AutofillResponseBuilder(form)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val formOrigin = form.formOrigin
|
private val formOrigin = form.formOrigin
|
||||||
|
@ -260,19 +258,19 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates and returns a suitable [FillResponse] to the Autofill framework. */
|
/** Creates and returns a suitable [FillResponse] to the Autofill framework. */
|
||||||
fun fillCredentials(
|
override fun fillCredentials(context: Context, fillRequest: FillRequest, callback: FillCallback) {
|
||||||
context: Context,
|
|
||||||
inlineSuggestionsRequest: InlineSuggestionsRequest?,
|
|
||||||
callback: FillCallback
|
|
||||||
) {
|
|
||||||
AutofillMatcher.getMatchesFor(context, formOrigin)
|
AutofillMatcher.getMatchesFor(context, formOrigin)
|
||||||
.fold(
|
.fold(
|
||||||
success = { matchedFiles ->
|
success = { matchedFiles ->
|
||||||
callback.onSuccess(makeFillResponse(context, inlineSuggestionsRequest, matchedFiles))
|
callback.onSuccess(
|
||||||
|
makeFillResponse(context, fillRequest.inlineSuggestionsRequest, matchedFiles)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
failure = { e ->
|
failure = { e ->
|
||||||
logcat(ERROR) { e.asLog() }
|
logcat(ERROR) { e.asLog() }
|
||||||
callback.onSuccess(makePublisherChangedResponse(context, inlineSuggestionsRequest, e))
|
callback.onSuccess(
|
||||||
|
makePublisherChangedResponse(context, fillRequest.inlineSuggestionsRequest, e)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,199 +1,27 @@
|
||||||
/*
|
|
||||||
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
package app.passwordstore.util.autofill
|
package app.passwordstore.util.autofill
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.IntentSender
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.service.autofill.Dataset
|
import android.service.autofill.Dataset
|
||||||
import android.service.autofill.FillCallback
|
import android.service.autofill.FillCallback
|
||||||
import android.service.autofill.FillResponse
|
import android.service.autofill.FillRequest
|
||||||
import android.service.autofill.SaveInfo
|
|
||||||
import app.passwordstore.autofill.oreo.ui.AutofillSmsActivity
|
|
||||||
import app.passwordstore.ui.autofill.AutofillDecryptActivity
|
|
||||||
import app.passwordstore.ui.autofill.AutofillFilterView
|
|
||||||
import app.passwordstore.ui.autofill.AutofillPublisherChangedActivity
|
|
||||||
import app.passwordstore.ui.autofill.AutofillSaveActivity
|
|
||||||
import com.github.androidpasswordstore.autofillparser.AutofillAction
|
import com.github.androidpasswordstore.autofillparser.AutofillAction
|
||||||
import com.github.androidpasswordstore.autofillparser.AutofillScenario
|
import com.github.androidpasswordstore.autofillparser.AutofillScenario
|
||||||
import com.github.androidpasswordstore.autofillparser.Credentials
|
import com.github.androidpasswordstore.autofillparser.Credentials
|
||||||
import com.github.androidpasswordstore.autofillparser.FillableForm
|
import com.github.androidpasswordstore.autofillparser.FillableForm
|
||||||
import com.github.androidpasswordstore.autofillparser.fillWith
|
import com.github.androidpasswordstore.autofillparser.fillWith
|
||||||
import com.github.michaelbull.result.fold
|
import logcat.LogPriority
|
||||||
import dagger.assisted.Assisted
|
|
||||||
import dagger.assisted.AssistedFactory
|
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import java.io.File
|
|
||||||
import logcat.LogPriority.ERROR
|
|
||||||
import logcat.asLog
|
|
||||||
import logcat.logcat
|
import logcat.logcat
|
||||||
|
|
||||||
class AutofillResponseBuilder
|
interface AutofillResponseBuilder {
|
||||||
@AssistedInject
|
fun fillCredentials(context: Context, fillRequest: FillRequest, callback: FillCallback)
|
||||||
constructor(
|
|
||||||
@Assisted form: FillableForm,
|
|
||||||
) {
|
|
||||||
|
|
||||||
@AssistedFactory
|
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(form: FillableForm): AutofillResponseBuilder
|
fun create(form: FillableForm): AutofillResponseBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
private val formOrigin = form.formOrigin
|
|
||||||
private val scenario = form.scenario
|
|
||||||
private val ignoredIds = form.ignoredIds
|
|
||||||
private val saveFlags = form.saveFlags
|
|
||||||
private val clientState = form.toClientState()
|
|
||||||
|
|
||||||
// We do not offer save when the only relevant field is a username field or there is no field.
|
|
||||||
private val scenarioSupportsSave = scenario.hasPasswordFieldsToSave
|
|
||||||
private val canBeSaved = saveFlags != null && scenarioSupportsSave
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
private fun makeIntentDataset(
|
|
||||||
context: Context,
|
|
||||||
action: AutofillAction,
|
|
||||||
intentSender: IntentSender,
|
|
||||||
metadata: DatasetMetadata,
|
|
||||||
): Dataset {
|
|
||||||
return Dataset.Builder(makeRemoteView(context, metadata)).run {
|
|
||||||
fillWith(scenario, action, credentials = null)
|
|
||||||
setAuthentication(intentSender)
|
|
||||||
build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeMatchDataset(context: Context, file: File): Dataset? {
|
|
||||||
if (!scenario.hasFieldsToFillOn(AutofillAction.Match)) return null
|
|
||||||
val metadata = makeFillMatchMetadata(context, file)
|
|
||||||
val intentSender = AutofillDecryptActivity.makeDecryptFileIntentSender(file, context)
|
|
||||||
return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeSearchDataset(context: Context): Dataset? {
|
|
||||||
if (!scenario.hasFieldsToFillOn(AutofillAction.Search)) return null
|
|
||||||
val metadata = makeSearchAndFillMetadata(context)
|
|
||||||
val intentSender = AutofillFilterView.makeMatchAndDecryptFileIntentSender(context, formOrigin)
|
|
||||||
return makeIntentDataset(context, AutofillAction.Search, intentSender, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeGenerateDataset(context: Context): Dataset? {
|
|
||||||
if (!scenario.hasFieldsToFillOn(AutofillAction.Generate)) return null
|
|
||||||
val metadata = makeGenerateAndFillMetadata(context)
|
|
||||||
val intentSender = AutofillSaveActivity.makeSaveIntentSender(context, null, formOrigin)
|
|
||||||
return makeIntentDataset(context, AutofillAction.Generate, intentSender, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeFillOtpFromSmsDataset(context: Context): Dataset? {
|
|
||||||
if (!scenario.hasFieldsToFillOn(AutofillAction.FillOtpFromSms)) return null
|
|
||||||
if (!AutofillSmsActivity.shouldOfferFillFromSms(context)) return null
|
|
||||||
val metadata = makeFillOtpFromSmsMetadata(context)
|
|
||||||
val intentSender = AutofillSmsActivity.makeFillOtpFromSmsIntentSender(context)
|
|
||||||
return makeIntentDataset(context, AutofillAction.FillOtpFromSms, intentSender, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makePublisherChangedDataset(
|
|
||||||
context: Context,
|
|
||||||
publisherChangedException: AutofillPublisherChangedException,
|
|
||||||
): Dataset {
|
|
||||||
val metadata = makeWarningMetadata(context)
|
|
||||||
// If the user decides to trust the new publisher, they can choose reset the list of
|
|
||||||
// matches. In this case we need to immediately show a new `FillResponse` as if the app were
|
|
||||||
// autofilled for the first time. This `FillResponse` needs to be returned as a result from
|
|
||||||
// `AutofillPublisherChangedActivity`, which is why we create and pass it on here.
|
|
||||||
val fillResponseAfterReset = makeFillResponse(context, emptyList())
|
|
||||||
val intentSender =
|
|
||||||
AutofillPublisherChangedActivity.makePublisherChangedIntentSender(
|
|
||||||
context,
|
|
||||||
publisherChangedException,
|
|
||||||
fillResponseAfterReset
|
|
||||||
)
|
|
||||||
return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makePublisherChangedResponse(
|
|
||||||
context: Context,
|
|
||||||
publisherChangedException: AutofillPublisherChangedException
|
|
||||||
): FillResponse {
|
|
||||||
return FillResponse.Builder().run {
|
|
||||||
addDataset(makePublisherChangedDataset(context, publisherChangedException))
|
|
||||||
setIgnoredIds(*ignoredIds.toTypedArray())
|
|
||||||
build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Support multi-step authentication flows in apps via FLAG_DELAY_SAVE
|
|
||||||
// See:
|
|
||||||
// https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE
|
|
||||||
private fun makeSaveInfo(): SaveInfo? {
|
|
||||||
if (!canBeSaved) return null
|
|
||||||
check(saveFlags != null)
|
|
||||||
val idsToSave = scenario.fieldsToSave.toTypedArray()
|
|
||||||
if (idsToSave.isEmpty()) return null
|
|
||||||
var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD
|
|
||||||
if (scenario.hasUsername) {
|
|
||||||
saveDataTypes = saveDataTypes or SaveInfo.SAVE_DATA_TYPE_USERNAME
|
|
||||||
}
|
|
||||||
return SaveInfo.Builder(saveDataTypes, idsToSave).run {
|
|
||||||
setFlags(saveFlags)
|
|
||||||
build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeFillResponse(context: Context, matchedFiles: List<File>): FillResponse? {
|
|
||||||
var datasetCount = 0
|
|
||||||
return FillResponse.Builder().run {
|
|
||||||
for (file in matchedFiles) {
|
|
||||||
makeMatchDataset(context, file)?.let {
|
|
||||||
datasetCount++
|
|
||||||
addDataset(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
makeGenerateDataset(context)?.let {
|
|
||||||
datasetCount++
|
|
||||||
addDataset(it)
|
|
||||||
}
|
|
||||||
makeFillOtpFromSmsDataset(context)?.let {
|
|
||||||
datasetCount++
|
|
||||||
addDataset(it)
|
|
||||||
}
|
|
||||||
makeSearchDataset(context)?.let {
|
|
||||||
datasetCount++
|
|
||||||
addDataset(it)
|
|
||||||
}
|
|
||||||
if (datasetCount == 0) return null
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
setHeader(
|
|
||||||
makeRemoteView(
|
|
||||||
context,
|
|
||||||
makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
makeSaveInfo()?.let { setSaveInfo(it) }
|
|
||||||
setClientState(clientState)
|
|
||||||
setIgnoredIds(*ignoredIds.toTypedArray())
|
|
||||||
build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Creates and returns a suitable [FillResponse] to the Autofill framework. */
|
|
||||||
fun fillCredentials(context: Context, callback: FillCallback) {
|
|
||||||
AutofillMatcher.getMatchesFor(context, formOrigin)
|
|
||||||
.fold(
|
|
||||||
success = { matchedFiles -> callback.onSuccess(makeFillResponse(context, matchedFiles)) },
|
|
||||||
failure = { e ->
|
|
||||||
logcat(ERROR) { e.asLog() }
|
|
||||||
callback.onSuccess(makePublisherChangedResponse(context, e))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun makeFillInDataset(
|
fun makeFillInDataset(
|
||||||
context: Context,
|
context: Context,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
|
@ -215,7 +43,7 @@ constructor(
|
||||||
}
|
}
|
||||||
return builder.run {
|
return builder.run {
|
||||||
if (scenario != null) fillWith(scenario, action, credentials)
|
if (scenario != null) fillWith(scenario, action, credentials)
|
||||||
else logcat(ERROR) { "Failed to recover scenario from client state" }
|
else logcat(LogPriority.ERROR) { "Failed to recover scenario from client state" }
|
||||||
build()
|
build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import android.service.autofill.SaveRequest
|
||||||
import app.passwordstore.BuildConfig
|
import app.passwordstore.BuildConfig
|
||||||
import app.passwordstore.R
|
import app.passwordstore.R
|
||||||
import app.passwordstore.ui.autofill.AutofillSaveActivity
|
import app.passwordstore.ui.autofill.AutofillSaveActivity
|
||||||
import app.passwordstore.util.autofill.Api30AutofillResponseBuilder
|
|
||||||
import app.passwordstore.util.autofill.AutofillResponseBuilder
|
import app.passwordstore.util.autofill.AutofillResponseBuilder
|
||||||
import app.passwordstore.util.extensions.getString
|
import app.passwordstore.util.extensions.getString
|
||||||
import app.passwordstore.util.extensions.hasFlag
|
import app.passwordstore.util.extensions.hasFlag
|
||||||
|
@ -56,7 +55,6 @@ class OreoAutofillService : AutofillService() {
|
||||||
private const val DISABLE_AUTOFILL_DURATION_MS = 1000 * 60 * 60 * 24L
|
private const val DISABLE_AUTOFILL_DURATION_MS = 1000 * 60 * 60 * 24L
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject lateinit var api30ResponseBuilderFactory: Api30AutofillResponseBuilder.Factory
|
|
||||||
@Inject lateinit var responseBuilderFactory: AutofillResponseBuilder.Factory
|
@Inject lateinit var responseBuilderFactory: AutofillResponseBuilder.Factory
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
@ -100,13 +98,7 @@ class OreoAutofillService : AutofillService() {
|
||||||
callback.onSuccess(null)
|
callback.onSuccess(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
responseBuilderFactory.create(formToFill).fillCredentials(this, request, callback)
|
||||||
api30ResponseBuilderFactory
|
|
||||||
.create(formToFill)
|
|
||||||
.fillCredentials(this, request.inlineSuggestionsRequest, callback)
|
|
||||||
} else {
|
|
||||||
responseBuilderFactory.create(formToFill).fillCredentials(this, callback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
|
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[versions]
|
[versions]
|
||||||
agp = "8.3.0-alpha04"
|
agp = "8.3.0-alpha05"
|
||||||
androidxActivity = "1.8.0-rc01"
|
androidxActivity = "1.8.0-rc01"
|
||||||
bouncycastle = "1.76"
|
bouncycastle = "1.76"
|
||||||
moshi = "1.15.0"
|
moshi = "1.15.0"
|
||||||
|
|
Loading…
Reference in a new issue