summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml2
-rw-r--r--packages/CredentialManager/res/drawable/more_options_list_item.xml31
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml42
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml9
-rw-r--r--packages/CredentialManager/res/values/colors.xml2
-rw-r--r--packages/CredentialManager/res/values/dimens.xml8
-rw-r--r--packages/CredentialManager/res/values/strings.xml5
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt121
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt42
9 files changed, 213 insertions, 49 deletions
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
index 5becc86927d2..f13402c7206d 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -23,7 +23,7 @@
android:shape="rectangle"
android:top="1dp">
<shape>
- <corners android:radius="16dp" />
+ <corners android:radius="4dp" />
<solid android:color="@color/dropdown_container" />
</shape>
</item>
diff --git a/packages/CredentialManager/res/drawable/more_options_list_item.xml b/packages/CredentialManager/res/drawable/more_options_list_item.xml
new file mode 100644
index 000000000000..d7b509ee48fd
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/more_options_list_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+ android:color="@android:color/transparent">
+ <item
+ android:bottom="1dp"
+ android:shape="rectangle"
+ android:top="1dp">
+ <shape>
+ <corners android:bottomLeftRadius="4dp"
+ android:bottomRightRadius="4dp"/>
+ <solid android:color="@color/sign_in_options_container" />
+ </shape>
+ </item>
+</ripple> \ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
new file mode 100644
index 000000000000..929756cdf9cc
--- /dev/null
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -0,0 +1,42 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
+ android:elevation="3dp">
+
+ <ImageView
+ android:id="@android:id/icon1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:contentDescription="@string/provider_icon_content_description"
+ android:background="@null"/>
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
+ style="@style/autofill.TextTitle"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index cb6c6b473244..1fe5e0ed41f9 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,22 +17,25 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxWidth="@dimen/autofill_dropdown_layout_width"
+ android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
<ImageView
android:id="@android:id/icon1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:contentDescription="@string/provider_icon_content_description"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:background="@null"/>
<TextView
android:id="@android:id/text1"
- android:layout_width="@dimen/autofill_dropdown_text_width"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
style="@style/autofill.TextTitle"/>
<TextView
android:id="@android:id/text2"
@@ -40,6 +43,8 @@
android:layout_height="wrap_content"
android:layout_below="@android:id/text1"
android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
style="@style/autofill.TextSubtitle"/>
</RelativeLayout>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index dcb7ef9c3ed8..7cb1d01972b7 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -20,4 +20,6 @@
<color name="text_primary">#1A1B20</color>
<color name="text_secondary">#44474F</color>
<color name="dropdown_container">#F3F3FA</color>
+ <color name="sign_in_options_container">#DADADA</color>
+ <color name="sign_in_options_icon_color">#1B1B1B</color>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 2a4719d027e2..3a8c78f6d854 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -18,11 +18,13 @@
<resources>
<dimen name="autofill_view_top_padding">12dp</dimen>
- <dimen name="autofill_view_right_padding">24dp</dimen>
+ <dimen name="autofill_view_right_padding">12dp</dimen>
<dimen name="autofill_view_bottom_padding">12dp</dimen>
<dimen name="autofill_view_left_padding">16dp</dimen>
<dimen name="autofill_view_icon_to_text_padding">10dp</dimen>
<dimen name="autofill_icon_size">24dp</dimen>
- <dimen name="autofill_dropdown_layout_width">296dp</dimen>
- <dimen name="autofill_dropdown_text_width">240dp</dimen>
+ <dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
+ <dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
+ <dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
+ <integer name="autofill_max_visible_datasets">3</integer>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 605e77bef34e..f98164b8788c 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -168,4 +168,9 @@
<string name="get_dialog_option_headline_use_a_different_device">Use a different device</string>
<!-- Text shown on a snackbar when the app cancelled the UI. [CHAR LIMIT=120] -->
<string name="request_cancelled_by">Request cancelled by <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+
+ <!-- Strings for dropdown presentation. -->
+ <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
+ <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
+ <string name="provider_icon_content_description">Credential provider icon</string>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 03ac605222ba..985f3228f402 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -30,6 +30,7 @@ import android.credentials.ui.GetCredentialProviderData
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
+import android.provider.Settings
import android.credentials.Credential
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
@@ -48,7 +49,9 @@ import android.view.autofill.IAutoFillManagerClient
import android.view.autofill.AutofillId
import android.widget.inline.InlinePresentationSpec
import android.credentials.CredentialManager
+import android.widget.RemoteViews
import androidx.autofill.inline.v1.InlineSuggestionUi
+import androidx.core.content.ContextCompat
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
@@ -115,7 +118,7 @@ class CredentialAutofillService : AutofillService() {
}
val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
- requestId)
+ requestId)
if (getCredRequest == null) {
Log.i(TAG, "No credential manager request found")
callback.onFailure("No credential manager request found")
@@ -307,10 +310,14 @@ class CredentialAutofillService : AutofillService() {
val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
- var maxItemCount = totalEntryCount
- if (inlineMaxSuggestedCount > 0) {
- maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount)
- }
+ val maxDropdownDisplayLimit = this.resources.getInteger(
+ com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
+ var maxInlineItemCount = totalEntryCount
+ maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
+ val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
+ Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
+ (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+
var i = 0
var datasetAdded = false
@@ -333,13 +340,8 @@ class CredentialAutofillService : AutofillService() {
Log.e(TAG, "PendingIntent was missing from the entry.")
return@usernameLoop
}
- if (inlinePresentationSpecs == null) {
- Log.i(TAG, "Inline presentation spec is null, " +
- "building dropdown presentation only")
- }
- if (i >= maxItemCount) {
- Log.e(TAG, "Skipping because reached the max item count.")
- return@usernameLoop
+ if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
+ return@usernameLoop;
}
val icon: Icon = if (primaryEntry.icon == null) {
// The empty entry icon has non-null icon reference but null drawable reference.
@@ -351,38 +353,26 @@ class CredentialAutofillService : AutofillService() {
}
// Create inline presentation
var inlinePresentation: InlinePresentation? = null
- var spec: InlinePresentationSpec?
- if (inlinePresentationSpecs != null) {
- if (i < inlinePresentationSpecsCount) {
- spec = inlinePresentationSpecs[i]
+ if (inlinePresentationSpecs != null && i < maxInlineItemCount) {
+ val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
+ inlinePresentationSpecs[i]
} else {
- spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+ inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
}
- val displayName: String = if (primaryEntry.credentialType ==
- CredentialType.PASSKEY && primaryEntry.displayName != null) {
- primaryEntry.displayName!!
- } else {
- primaryEntry.userName
- }
- val sliceBuilder = InlineSuggestionUi
- .newContentBuilder(pendingIntent)
- .setTitle(displayName)
- sliceBuilder.setStartIcon(icon)
- if (primaryEntry.credentialType ==
- CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[displayName]
- == true) {
- sliceBuilder.setSubtitle(primaryEntry.userName)
- }
- inlinePresentation = InlinePresentation(
- sliceBuilder.build().slice, spec, /* pinned= */ false)
+ inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon,
+ spec!!, duplicateDisplayNamesForPasskeys)
+ }
+ var dropdownPresentation: RemoteViews? = null
+ if (i < lastDropdownDatasetIndex) {
+ dropdownPresentation = RemoteViewsFactory
+ .createDropdownPresentation(this, icon, primaryEntry)
}
- val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
- this, icon, primaryEntry)
- i++
val dataSetBuilder = Dataset.Builder()
val presentationBuilder = Presentations.Builder()
- .setMenuPresentation(dropdownPresentation)
+ if (dropdownPresentation != null) {
+ presentationBuilder.setMenuPresentation(dropdownPresentation)
+ }
if (inlinePresentation != null) {
presentationBuilder.setInlinePresentation(inlinePresentation)
}
@@ -398,6 +388,12 @@ class CredentialAutofillService : AutofillService() {
.setAuthenticationExtras(fillInIntent.extras)
.build())
datasetAdded = true
+ i++
+
+ if (i == lastDropdownDatasetIndex && bottomSheetPendingIntent != null) {
+ addDropdownMoreOptionsPresentation(bottomSheetPendingIntent, autofillId,
+ fillResponseBuilder)
+ }
}
val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
inlinePresentationSpecsCount)
@@ -408,6 +404,49 @@ class CredentialAutofillService : AutofillService() {
return datasetAdded
}
+ private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
+ pendingIntent: PendingIntent,
+ icon: Icon,
+ spec: InlinePresentationSpec,
+ duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
+ InlinePresentation {
+ val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
+ && primaryEntry.displayName != null) {
+ primaryEntry.displayName!!
+ } else {
+ primaryEntry.userName
+ }
+ val sliceBuilder = InlineSuggestionUi
+ .newContentBuilder(pendingIntent)
+ .setTitle(displayName)
+ sliceBuilder.setStartIcon(icon)
+ if (primaryEntry.credentialType ==
+ CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
+ sliceBuilder.setSubtitle(primaryEntry.userName)
+ }
+ return InlinePresentation(
+ sliceBuilder.build().slice, spec, /* pinned= */ false)
+ }
+
+ private fun addDropdownMoreOptionsPresentation(
+ bottomSheetPendingIntent: PendingIntent,
+ autofillId: AutofillId,
+ fillResponseBuilder: FillResponse.Builder) {
+ val presentationBuilder = Presentations.Builder()
+ .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+
+ fillResponseBuilder.addDataset(
+ Dataset.Builder()
+ .setField(
+ autofillId,
+ Field.Builder().setPresentations(
+ presentationBuilder.build())
+ .build())
+ .setAuthentication(bottomSheetPendingIntent.intentSender)
+ .build()
+ )
+ }
+
private fun getLastInlinePresentationSpec(
inlinePresentationSpecs: List<InlinePresentationSpec>?,
inlinePresentationSpecsCount: Int
@@ -534,9 +573,9 @@ class CredentialAutofillService : AutofillService() {
}
private fun getCredManRequest(
- structure: AssistStructure,
- sessionId: Int,
- requestId: Int
+ structure: AssistStructure,
+ sessionId: Int,
+ requestId: Int
): GetCredentialRequest? {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
traverseStructure(structure, credentialOptions)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index e039dead043e..68f1c861d51b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -44,7 +44,7 @@ class RemoteViewsFactory {
if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
return remoteViews
}
- setRemoteViewsPaddings(remoteViews, context)
+ setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0)
if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) {
val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
remoteViews.setTextViewText(android.R.id.text1, displayName)
@@ -81,8 +81,46 @@ class RemoteViewsFactory {
return remoteViews
}
+ fun createMoreSignInOptionsPresentation(context: Context): RemoteViews {
+ var layoutId: Int = com.android.credentialmanager.R.layout
+ .credman_dropdown_bottom_sheet
+ val remoteViews = RemoteViews(context.packageName, layoutId)
+ setRemoteViewsPaddings(remoteViews, context)
+ remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context,
+ com.android.credentialmanager
+ .R.string.dropdown_presentation_more_sign_in_options_text))
+
+ val textColorPrimary = ContextCompat.getColor(context,
+ com.android.credentialmanager.R.color.text_primary)
+ remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
+ val icon = Icon.createWithResource(context, com
+ .android.credentialmanager.R.drawable.more_horiz_24px)
+ icon.setTint(ContextCompat.getColor(context,
+ com.android.credentialmanager.R.color.sign_in_options_icon_color))
+ remoteViews.setImageViewIcon(android.R.id.icon1, icon)
+ remoteViews.setBoolean(
+ android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+ remoteViews.setInt(
+ android.R.id.icon1,
+ setMaxHeightMethodName,
+ context.resources.getDimensionPixelSize(
+ com.android.credentialmanager.R.dimen.autofill_icon_size));
+ val drawableId =
+ com.android.credentialmanager.R.drawable.more_options_list_item
+ remoteViews.setInt(
+ android.R.id.content, setBackgroundResourceMethodName, drawableId);
+ return remoteViews
+ }
+
private fun setRemoteViewsPaddings(
remoteViews: RemoteViews, context: Context) {
+ val bottomPadding = context.resources.getDimensionPixelSize(
+ com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
+ setRemoteViewsPaddings(remoteViews, context, bottomPadding)
+ }
+
+ private fun setRemoteViewsPaddings(
+ remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) {
val leftPadding = context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_view_left_padding)
val iconToTextPadding = context.resources.getDimensionPixelSize(
@@ -104,7 +142,7 @@ class RemoteViewsFactory {
iconToTextPadding,
/* top=*/topPadding,
/* right=*/rightPadding,
- /* bottom=*/0)
+ primaryTextBottomPadding)
remoteViews.setViewPadding(
android.R.id.text2,
iconToTextPadding,