diff options
9 files changed, 354 insertions, 31 deletions
diff --git a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml b/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml new file mode 100644 index 000000000000..9d16f32db932 --- /dev/null +++ b/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2023 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. + --> + +<!-- Copied from //frameworks/base/core/res/res/drawable/item_background_material.xml --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/autofill_light_colorControlHighlight"> + <item android:id="@android:id/mask"> + <color android:color="@android:color/white"/> + </item> +</ripple>
\ No newline at end of file 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 new file mode 100644 index 000000000000..2f0c83b6556a --- /dev/null +++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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:radius="28dp" /> + <solid android:color="@android:color/system_surface_container_high_light" /> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml new file mode 100644 index 000000000000..39f49caa7051 --- /dev/null +++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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:radius="28dp" /> + <solid android:color="@android:color/system_surface_container_high_dark" /> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml new file mode 100644 index 000000000000..e4e9f7ac85a9 --- /dev/null +++ b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml @@ -0,0 +1,37 @@ +<!-- + ~ Copyright (C) 2023 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="match_parent" + android:layout_height="wrap_content" + style="@style/autofill.Dataset"> + <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: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" + style="@style/autofill.TextAppearance"/> + +</RelativeLayout> diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml new file mode 100644 index 000000000000..63b9f24d9033 --- /dev/null +++ b/packages/CredentialManager/res/values/colors.xml @@ -0,0 +1,38 @@ +<!-- + ~ Copyright (C) 2023 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. + --> + +<!-- Color palette --> +<resources> + <color name="autofill_light_colorPrimary">@color/primary_material_light</color> + <color name="autofill_light_colorAccent">@color/accent_material_light</color> + <color name="autofill_light_colorControlHighlight">@color/ripple_material_light</color> + <color name="autofill_light_colorButtonNormal">@color/button_material_light</color> + + <!-- Text colors --> + <color name="autofill_light_textColorPrimary">@color/abc_primary_text_material_light</color> + <color name="autofill_light_textColorSecondary">@color/abc_secondary_text_material_light</color> + <color name="autofill_light_textColorHint">@color/abc_hint_foreground_material_light</color> + <color name="autofill_light_textColorHintInverse">@color/abc_hint_foreground_material_dark + </color> + <color name="autofill_light_textColorHighlight">@color/highlighted_text_material_light</color> + <color name="autofill_light_textColorLink">@color/autofill_light_colorAccent</color> + + <!-- These colors are used for Remote Views. --> + <color name="background_dark_mode">#0E0C0B</color> + <color name="background">#F1F3F4</color> + <color name="text_primary_dark_mode">#DFDEDB</color> + <color name="text_primary">#202124</color> +</resources>
\ No newline at end of file diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml new file mode 100644 index 000000000000..67003a330974 --- /dev/null +++ b/packages/CredentialManager/res/values/dimens.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2023 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. + --> + +<resources> + <dimen name="autofill_view_padding">16dp</dimen> + <dimen name="autofill_icon_size">16dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/CredentialManager/res/values/styles.xml b/packages/CredentialManager/res/values/styles.xml new file mode 100644 index 000000000000..4a5761acdd83 --- /dev/null +++ b/packages/CredentialManager/res/values/styles.xml @@ -0,0 +1,38 @@ +<!-- + ~ Copyright (C) 2023 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. + --> + +<resources> + <style name="autofill.TextAppearance.Small" parent="@style/autofill.TextAppearance"> + <item name="android:textSize">12sp</item> + </style> + + + <style name="autofill.Dataset" parent=""> + <item name="android:background">@drawable/autofill_light_selectable_item_background</item> + </style> + + <style name="autofill.TextAppearance" parent=""> + <item name="android:textColor">@color/autofill_light_textColorPrimary</item> + <item name="android:textColorHint">@color/autofill_light_textColorHint</item> + <item name="android:textColorHighlight">@color/autofill_light_textColorHighlight</item> + <item name="android:textColorLink">@color/autofill_light_textColorLink</item> + <item name="android:textSize">14sp</item> + </style> + + <style name="autofill.TextAppearance.Primary"> + <item name="android:textColor">@color/autofill_light_textColorPrimary</item> + </style> +</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 20d2f09ced8f..0ff1c7fd8953 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -16,6 +16,7 @@ package com.android.credentialmanager.autofill +import android.R import android.app.assist.AssistStructure import android.content.Context import android.credentials.CredentialManager @@ -41,18 +42,19 @@ import android.service.autofill.SaveRequest import android.service.credentials.CredentialProviderService import android.util.Log import android.view.autofill.AutofillId -import org.json.JSONException import android.widget.inline.InlinePresentationSpec import androidx.autofill.inline.v1.InlineSuggestionUi import androidx.credentials.provider.CustomCredentialEntry import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry import com.android.credentialmanager.GetFlowUtils -import com.android.credentialmanager.model.get.CredentialEntryInfo +import com.android.credentialmanager.common.ui.RemoteViewsFactory import com.android.credentialmanager.getflow.ProviderDisplayInfo -import com.android.credentialmanager.model.get.ProviderInfo import com.android.credentialmanager.getflow.toProviderDisplayInfo import com.android.credentialmanager.ktx.credentialEntry +import com.android.credentialmanager.model.get.CredentialEntryInfo +import com.android.credentialmanager.model.get.ProviderInfo +import org.json.JSONException import org.json.JSONObject import java.util.concurrent.Executors @@ -127,9 +129,11 @@ class CredentialAutofillService : AutofillService() { is PasswordCredentialEntry -> { entryIconMap[entry.key + entry.subkey] = credentialEntry.icon } + is PublicKeyCredentialEntry -> { entryIconMap[entry.key + entry.subkey] = credentialEntry.icon } + is CustomCredentialEntry -> { entryIconMap[entry.key + entry.subkey] = credentialEntry.icon } @@ -172,11 +176,11 @@ class CredentialAutofillService : AutofillService() { } private fun processProvidersForAutofillId( - filLRequest: FillRequest, - autofillId: AutofillId, - providerList: List<ProviderInfo>, - entryIconMap: Map<String, Icon>, - fillResponseBuilder: FillResponse.Builder + filLRequest: FillRequest, + autofillId: AutofillId, + providerList: List<ProviderInfo>, + entryIconMap: Map<String, Icon>, + fillResponseBuilder: FillResponse.Builder ): Boolean { if (providerList.isEmpty()) { return false @@ -197,7 +201,7 @@ class CredentialAutofillService : AutofillService() { var i = 0 var datasetAdded = false - providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ { + providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{ val primaryEntry = it.sortedCredentialEntryList.first() val pendingIntent = primaryEntry.pendingIntent val fillInIntent = primaryEntry.fillInIntent @@ -206,37 +210,48 @@ class CredentialAutofillService : AutofillService() { Log.e(TAG, "PendingIntent was missing from the entry.") return@usernameLoop } - if (inlinePresentationSpecs == null || i >= maxItemCount) { + 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 } - // Create inline presentation - val spec: InlinePresentationSpec - if (i < inlinePresentationSpecsCount) { - spec = inlinePresentationSpecs[i] - } else { - spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1] - } - val sliceBuilder = InlineSuggestionUi - .newContentBuilder(pendingIntent) - .setTitle(primaryEntry.userName) - val icon: Icon - if (primaryEntry.icon == null) { + val icon: Icon = if (primaryEntry.icon == null) { // The empty entry icon has non-null icon reference but null drawable reference. // If the drawable reference is null, then use the default icon. - icon = getDefaultIcon() + getDefaultIcon() } else { - icon = entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey] + entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey] ?: getDefaultIcon() } - sliceBuilder.setStartIcon(icon) - val inlinePresentation = InlinePresentation( - sliceBuilder.build().slice, spec, /* pinned= */ false) + // Create inline presentation + var inlinePresentation: InlinePresentation? = null; + if (inlinePresentationSpecs != null) { + val spec: InlinePresentationSpec + if (i < inlinePresentationSpecsCount) { + spec = inlinePresentationSpecs[i] + } else { + spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1] + } + val sliceBuilder = InlineSuggestionUi + .newContentBuilder(pendingIntent) + .setTitle(primaryEntry.userName) + sliceBuilder.setStartIcon(icon) + inlinePresentation = InlinePresentation( + sliceBuilder.build().slice, spec, /* pinned= */ false) + } + val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation( + this, icon, primaryEntry) i++ val dataSetBuilder = Dataset.Builder() val presentationBuilder = Presentations.Builder() - .setInlinePresentation(inlinePresentation) + .setMenuPresentation(dropdownPresentation) + if (inlinePresentation != null) { + presentationBuilder.setInlinePresentation(inlinePresentation) + } fillResponseBuilder.addDataset( dataSetBuilder @@ -305,7 +320,7 @@ class CredentialAutofillService : AutofillService() { ): MutableMap<AutofillId, MutableList<CredentialEntryInfo>> { val autofillIdToCredentialEntries: MutableMap<AutofillId, MutableList<CredentialEntryInfo>> = mutableMapOf() - credentialEntryList.forEach entryLoop@ { credentialEntry -> + credentialEntryList.forEach entryLoop@{ credentialEntry -> val autofillId: AutofillId? = credentialEntry .fillInIntent ?.getParcelableExtra( @@ -323,8 +338,8 @@ class CredentialAutofillService : AutofillService() { } private fun copyProviderInfo( - providerInfo: ProviderInfo, - credentialList: List<CredentialEntryInfo> + providerInfo: ProviderInfo, + credentialList: List<CredentialEntryInfo> ): ProviderInfo { return ProviderInfo( providerInfo.id, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt new file mode 100644 index 000000000000..4dc7f00c1550 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.android.credentialmanager.common.ui + +import android.content.Context +import android.content.res.Configuration +import android.widget.RemoteViews +import androidx.core.content.ContextCompat +import com.android.credentialmanager.model.get.CredentialEntryInfo +import android.graphics.drawable.Icon + +class RemoteViewsFactory { + + companion object { + private const val setAdjustViewBoundsMethodName = "setAdjustViewBounds" + private const val setMaxHeightMethodName = "setMaxHeight" + private const val setBackgroundResourceMethodName = "setBackgroundResource" + + fun createDropdownPresentation( + context: Context, + icon: Icon, + credentialEntryInfo: CredentialEntryInfo + ): RemoteViews { + val padding = context.resources.getDimensionPixelSize(com.android + .credentialmanager.R.dimen.autofill_view_padding) + var layoutId: Int = com.android.credentialmanager.R.layout + .autofill_dataset_left_with_item_tag_hint + val remoteViews = RemoteViews(context.packageName, layoutId) + setRemoteViewsPaddings(remoteViews, padding) + val textColorPrimary = getTextColorPrimary(isDarkMode(context), context); + remoteViews.setTextColor(android.R.id.text1, textColorPrimary); + remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName) + + 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 = if (isDarkMode(context)) + com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one_dark + else com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one + remoteViews.setInt( + android.R.id.content, setBackgroundResourceMethodName, drawableId); + return remoteViews + } + + private fun setRemoteViewsPaddings( + remoteViews: RemoteViews, + padding: Int) { + val halfPadding = padding / 2 + remoteViews.setViewPadding( + android.R.id.text1, + halfPadding, + halfPadding, + halfPadding, + halfPadding) + } + + private fun isDarkMode(context: Context): Boolean { + val currentNightMode = context.resources.configuration.uiMode and + Configuration.UI_MODE_NIGHT_MASK + return currentNightMode == Configuration.UI_MODE_NIGHT_YES + } + + private fun getTextColorPrimary(darkMode: Boolean, context: Context): Int { + return if (darkMode) ContextCompat.getColor( + context, com.android.credentialmanager.R.color.text_primary_dark_mode) + else ContextCompat.getColor(context, com.android.credentialmanager.R.color.text_primary) + } + } +} |