Manual selection for inline suggestions

This commit is contained in:
Ulrich Dürholz
2021-08-29 10:16:15 +02:00
parent c71ef24052
commit f4342f1448
6 changed files with 73 additions and 21 deletions

View File

@@ -206,9 +206,9 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO"
fun getAuthIntentSenderForSelection(context: Context,
searchInfo: SearchInfo? = null,
inlineSuggestionsRequest: InlineSuggestionsRequest? = null): IntentSender {
fun getPendingIntentForSelection(context: Context,
searchInfo: SearchInfo? = null,
inlineSuggestionsRequest: InlineSuggestionsRequest? = null): PendingIntent {
return PendingIntent.getActivity(context, 0,
// Doesn't work with Parcelable (don't know why?)
Intent(context, AutofillLauncherActivity::class.java).apply {
@@ -224,7 +224,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
}
}
},
PendingIntent.FLAG_CANCEL_CURRENT).intentSender
PendingIntent.FLAG_CANCEL_CURRENT)
}
fun getAuthIntentSenderForRegistration(context: Context,

View File

@@ -25,6 +25,7 @@ import android.app.PendingIntent
import android.app.assist.AssistStructure
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.graphics.BlendMode
import android.graphics.drawable.Icon
import android.os.Build
@@ -37,6 +38,7 @@ import android.view.autofill.AutofillValue
import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.RemoteViews
import android.widget.Toast
import android.widget.inline.InlinePresentationSpec
import androidx.annotation.RequiresApi
import androidx.autofill.inline.UiVersions
import androidx.autofill.inline.v1.InlineSuggestionUi
@@ -52,7 +54,6 @@ import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
import kotlin.collections.ArrayList
@@ -273,6 +274,27 @@ object AutofillHelper {
return null
}
@RequiresApi(Build.VERSION_CODES.R)
@SuppressLint("RestrictedApi")
private fun buildInlinePresentationForManualSelection(context: Context,
inlinePresentationSpec: InlinePresentationSpec,
pendingIntent: PendingIntent): InlinePresentation? {
// Make sure that the IME spec claims support for v1 UI template.
val imeStyle = inlinePresentationSpec.style
if (!UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1))
return null
// Build the content for IME UI
return InlinePresentation(
InlineSuggestionUi.newContentBuilder(pendingIntent).apply {
setContentDescription(context.getString(R.string.autofill_sign_in_prompt))
setTitle(context.getString(R.string.autofill_manual_selection_prompt))
setStartIcon(Icon.createWithResource(context, R.drawable.ic_arrow_right_green_24dp).apply {
setTintBlendMode(BlendMode.DST)
})
}.build().slice, inlinePresentationSpec, false)
}
fun buildResponse(context: Context,
database: Database,
entriesInfo: List<EntryInfo>,
@@ -296,18 +318,28 @@ object AutofillHelper {
}
// Add inline suggestion for new IME and dataset
entriesInfo.forEachIndexed { index, entryInfo ->
val inlinePresentation = inlineSuggestionsRequest?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
buildInlinePresentationForEntry(context, database, inlineSuggestionsRequest, index, entryInfo)
} else {
null
var numberInlineSuggestions = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
inlineSuggestionsRequest?.let {
numberInlineSuggestions = minOf(inlineSuggestionsRequest.maxSuggestionCount, entriesInfo.size)
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
if (entriesInfo.size >= inlineSuggestionsRequest.maxSuggestionCount) {
--numberInlineSuggestions
}
}
}
val dataSet = buildDataset(context, database, entryInfo, parseResult, inlinePresentation)
dataSet?.let {
responseBuilder.addDataset(it)
}
entriesInfo.forEachIndexed { _, entry ->
val inlinePresentation = if (numberInlineSuggestions > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
inlineSuggestionsRequest?.let {
buildInlinePresentationForEntry(context, database, inlineSuggestionsRequest, numberInlineSuggestions--, entry)
}
} else {
null
}
responseBuilder.addDataset(buildDataset(context, database, entry, parseResult, inlinePresentation))
}
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
@@ -319,16 +351,26 @@ object AutofillHelper {
}
val manualSelectionView = RemoteViews(context.packageName, R.layout.item_autofill_entry)
manualSelectionView.setTextViewText(R.id.autofill_entry_text, context.getString(R.string.autofill_manual_selection_prompt))
manualSelectionView.setImageViewResource(R.id.autofill_entry_icon, R.mipmap.ic_launcher_round)
val intentSender = AutofillLauncherActivity.getAuthIntentSenderForSelection(context,
// manualSelectionView.setImageViewResource(R.id.autofill_entry_icon, R.mipmap.ic_launcher_round)
val pendingIntent = AutofillLauncherActivity.getPendingIntentForSelection(context,
searchInfo, inlineSuggestionsRequest)
val builder = Dataset.Builder(manualSelectionView)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
inlineSuggestionsRequest?.let {
val inlinePresentationSpec = inlineSuggestionsRequest.inlinePresentationSpecs[0]
val inlinePresentation = buildInlinePresentationForManualSelection(context, inlinePresentationSpec, pendingIntent)
inlinePresentation?.let {
builder.setInlinePresentation(it)
}
}
}
// enable manual selection only for the form field that has focus
parseResult.focusedId?.let { autofillId ->
builder.setValue(autofillId, AutofillValue.forText("dummy"))
builder.setAuthentication(intentSender)
builder.setAuthentication(pendingIntent.intentSender)
responseBuilder.addDataset(builder.build())
}
}
@@ -371,6 +413,7 @@ object AutofillHelper {
} else {
buildResponse(activity, database, entriesInfo, result, null)
}
response.apply { }
val mReplyIntent = Intent()
Log.d(activity.javaClass.name, "Successed Autofill auth.")
mReplyIntent.putExtra(

View File

@@ -160,8 +160,8 @@ class KeeAutofillService : AutofillService() {
if (autofillIds.isNotEmpty()) {
// If the entire Autofill Response is authenticated, AuthActivity is used
// to generate Response.
val intentSender = AutofillLauncherActivity.getAuthIntentSenderForSelection(this,
searchInfo, inlineSuggestionsRequest)
val intentSender = AutofillLauncherActivity.getPendingIntentForSelection(this,
searchInfo, inlineSuggestionsRequest).intentSender
val responseBuilder = FillResponse.Builder()
val remoteViewsUnlock: RemoteViews = if (!parseResult.webDomain.isNullOrEmpty()) {
RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply {

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@color/green"
android:pathData="M10,17l5,-5 -5,-5v10z"/>
</vector>

View File

@@ -31,7 +31,7 @@
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:contentDescription="@string/content_description_entry_icon"
android:src="@drawable/ic_key_white_24dp" />
android:src="@drawable/ic_arrow_right_green_24dp" />
<TextView
android:id="@+id/autofill_entry_text"

View File

@@ -487,7 +487,7 @@
<string name="autofill_auto_search_summary">Automatically suggest search results from the web domain or application ID</string>
<string name="autofill_inline_suggestions_title">Inline suggestions</string>
<string name="autofill_inline_suggestions_summary">Attempt to display autofill suggestions directly from a compatible keyboard</string>
<string name="autofill_manual_selection_prompt">Choose entry...</string>
<string name="autofill_manual_selection_prompt">Select entry...</string>
<string name="autofill_manual_selection_title">Manual selection</string>
<string name="autofill_manual_selection_summary">Display option to let the user select database entry</string>
<string name="autofill_save_search_info_title">Save search info</string>