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

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

View file

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

View file

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