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:
renovate[bot] 2023-09-22 09:56:07 +05:30 committed by GitHub
parent 079be4a810
commit da17f0b4fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 237 additions and 204 deletions

View file

@ -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
}
}
}

View file

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

View file

@ -10,6 +10,7 @@ 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.Presentations
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.fillWith
import com.github.michaelbull.result.fold
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.io.File
import logcat.LogPriority.ERROR
import logcat.asLog
@ -36,14 +34,14 @@ import logcat.logcat
/** Implements [AutofillResponseBuilder]'s methods for API 30 and above */
@RequiresApi(Build.VERSION_CODES.R)
class Api30AutofillResponseBuilder
@AssistedInject
constructor(
@Assisted form: FillableForm,
) {
private constructor(
form: FillableForm,
) : AutofillResponseBuilder {
@AssistedFactory
interface Factory {
fun create(form: FillableForm): Api30AutofillResponseBuilder
object Factory : AutofillResponseBuilder.Factory {
override fun create(
form: FillableForm,
): AutofillResponseBuilder = Api30AutofillResponseBuilder(form)
}
private val formOrigin = form.formOrigin
@ -260,19 +258,19 @@ constructor(
}
/** Creates and returns a suitable [FillResponse] to the Autofill framework. */
fun fillCredentials(
context: Context,
inlineSuggestionsRequest: InlineSuggestionsRequest?,
callback: FillCallback
) {
override fun fillCredentials(context: Context, fillRequest: FillRequest, callback: FillCallback) {
AutofillMatcher.getMatchesFor(context, formOrigin)
.fold(
success = { matchedFiles ->
callback.onSuccess(makeFillResponse(context, inlineSuggestionsRequest, matchedFiles))
callback.onSuccess(
makeFillResponse(context, fillRequest.inlineSuggestionsRequest, matchedFiles)
)
},
failure = { e ->
logcat(ERROR) { e.asLog() }
callback.onSuccess(makePublisherChangedResponse(context, inlineSuggestionsRequest, e))
callback.onSuccess(
makePublisherChangedResponse(context, fillRequest.inlineSuggestionsRequest, e)
)
}
)
}

View file

@ -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
import android.content.Context
import android.content.IntentSender
import android.os.Build
import android.os.Bundle
import android.service.autofill.Dataset
import android.service.autofill.FillCallback
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 android.service.autofill.FillRequest
import com.github.androidpasswordstore.autofillparser.AutofillAction
import com.github.androidpasswordstore.autofillparser.AutofillScenario
import com.github.androidpasswordstore.autofillparser.Credentials
import com.github.androidpasswordstore.autofillparser.FillableForm
import com.github.androidpasswordstore.autofillparser.fillWith
import com.github.michaelbull.result.fold
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.LogPriority
import logcat.logcat
class AutofillResponseBuilder
@AssistedInject
constructor(
@Assisted form: FillableForm,
) {
interface AutofillResponseBuilder {
fun fillCredentials(context: Context, fillRequest: FillRequest, callback: FillCallback)
@AssistedFactory
interface Factory {
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 {
fun makeFillInDataset(
context: Context,
credentials: Credentials,
@ -215,7 +43,7 @@ constructor(
}
return builder.run {
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()
}
}

View file

@ -16,7 +16,6 @@ import android.service.autofill.SaveRequest
import app.passwordstore.BuildConfig
import app.passwordstore.R
import app.passwordstore.ui.autofill.AutofillSaveActivity
import app.passwordstore.util.autofill.Api30AutofillResponseBuilder
import app.passwordstore.util.autofill.AutofillResponseBuilder
import app.passwordstore.util.extensions.getString
import app.passwordstore.util.extensions.hasFlag
@ -56,7 +55,6 @@ class OreoAutofillService : AutofillService() {
private const val DISABLE_AUTOFILL_DURATION_MS = 1000 * 60 * 60 * 24L
}
@Inject lateinit var api30ResponseBuilderFactory: Api30AutofillResponseBuilder.Factory
@Inject lateinit var responseBuilderFactory: AutofillResponseBuilder.Factory
override fun onCreate() {
@ -100,13 +98,7 @@ class OreoAutofillService : AutofillService() {
callback.onSuccess(null)
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
api30ResponseBuilderFactory
.create(formToFill)
.fillCredentials(this, request.inlineSuggestionsRequest, callback)
} else {
responseBuilderFactory.create(formToFill).fillCredentials(this, callback)
}
responseBuilderFactory.create(formToFill).fillCredentials(this, request, callback)
}
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {

View file

@ -1,5 +1,5 @@
[versions]
agp = "8.3.0-alpha04"
agp = "8.3.0-alpha05"
androidxActivity = "1.8.0-rc01"
bouncycastle = "1.76"
moshi = "1.15.0"