Merge "Update get flow about the icons and chip UI"
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 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.
|
||||
-->
|
||||
|
||||
<!--TODO: Testing only icon. Remove later. -->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="VectorPath"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#808080"
|
||||
android:pathData="M9.025,14.275Q8.5,14.275 8.125,13.9Q7.75,13.525 7.75,13Q7.75,12.475 8.125,12.1Q8.5,11.725 9.025,11.725Q9.575,11.725 9.938,12.1Q10.3,12.475 10.3,13Q10.3,13.525 9.938,13.9Q9.575,14.275 9.025,14.275ZM14.975,14.275Q14.425,14.275 14.062,13.9Q13.7,13.525 13.7,13Q13.7,12.475 14.062,12.1Q14.425,11.725 14.975,11.725Q15.5,11.725 15.875,12.1Q16.25,12.475 16.25,13Q16.25,13.525 15.875,13.9Q15.5,14.275 14.975,14.275ZM12,19.925Q15.325,19.925 17.625,17.625Q19.925,15.325 19.925,12Q19.925,11.4 19.85,10.85Q19.775,10.3 19.575,9.775Q19.05,9.9 18.538,9.962Q18.025,10.025 17.45,10.025Q15.2,10.025 13.188,9.062Q11.175,8.1 9.775,6.375Q8.975,8.3 7.5,9.712Q6.025,11.125 4.075,11.85Q4.075,11.9 4.075,11.925Q4.075,11.95 4.075,12Q4.075,15.325 6.375,17.625Q8.675,19.925 12,19.925ZM12,22.2Q9.9,22.2 8.038,21.4Q6.175,20.6 4.788,19.225Q3.4,17.85 2.6,15.988Q1.8,14.125 1.8,12Q1.8,9.875 2.6,8.012Q3.4,6.15 4.788,4.775Q6.175,3.4 8.038,2.6Q9.9,1.8 12,1.8Q14.125,1.8 15.988,2.6Q17.85,3.4 19.225,4.775Q20.6,6.15 21.4,8.012Q22.2,9.875 22.2,12Q22.2,14.125 21.4,15.988Q20.6,17.85 19.225,19.225Q17.85,20.6 15.988,21.4Q14.125,22.2 12,22.2Z"/>
|
||||
</vector>
|
||||
@@ -1,30 +0,0 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 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.
|
||||
-->
|
||||
|
||||
<!--TODO: Testing only icon. Remove later. -->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="VectorPath"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#808080"
|
||||
android:pathData="M16.1,21.2 L15.775,19.675Q15.5,19.55 15.25,19.425Q15,19.3 14.75,19.1L13.275,19.575L12.2,17.75L13.375,16.725Q13.325,16.4 13.325,16.112Q13.325,15.825 13.375,15.5L12.2,14.475L13.275,12.65L14.75,13.1Q15,12.925 15.25,12.787Q15.5,12.65 15.775,12.55L16.1,11.025H18.25L18.55,12.55Q18.825,12.65 19.075,12.8Q19.325,12.95 19.575,13.15L21.05,12.65L22.125,14.525L20.95,15.55Q21.025,15.825 21.013,16.137Q21,16.45 20.95,16.725L22.125,17.75L21.05,19.575L19.575,19.1Q19.325,19.3 19.075,19.425Q18.825,19.55 18.55,19.675L18.25,21.2ZM1.8,20.3V17.3Q1.8,16.375 2.275,15.613Q2.75,14.85 3.5,14.475Q4.775,13.825 6.425,13.362Q8.075,12.9 10,12.9Q10.2,12.9 10.4,12.9Q10.6,12.9 10.775,12.95Q9.925,14.85 10.062,16.738Q10.2,18.625 11.4,20.3ZM17.175,18.075Q17.975,18.075 18.55,17.487Q19.125,16.9 19.125,16.1Q19.125,15.3 18.55,14.725Q17.975,14.15 17.175,14.15Q16.375,14.15 15.788,14.725Q15.2,15.3 15.2,16.1Q15.2,16.9 15.788,17.487Q16.375,18.075 17.175,18.075ZM10,11.9Q8.25,11.9 7.025,10.662Q5.8,9.425 5.8,7.7Q5.8,5.95 7.025,4.725Q8.25,3.5 10,3.5Q11.75,3.5 12.975,4.725Q14.2,5.95 14.2,7.7Q14.2,9.425 12.975,10.662Q11.75,11.9 10,11.9Z"/>
|
||||
</vector>
|
||||
@@ -63,7 +63,7 @@ class CredentialManagerRepo(
|
||||
requestInfo = intent.extras?.getParcelable(
|
||||
RequestInfo.EXTRA_REQUEST_INFO,
|
||||
RequestInfo::class.java
|
||||
) ?: testCreatePasskeyRequestInfo()
|
||||
) ?: testGetRequestInfo()
|
||||
|
||||
providerEnabledList = when (requestInfo.type) {
|
||||
RequestInfo.TYPE_CREATE ->
|
||||
@@ -245,12 +245,10 @@ class CredentialManagerRepo(
|
||||
listOf(
|
||||
newActionEntry(
|
||||
"key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL,
|
||||
Icon.createWithResource(context, R.drawable.ic_manage_accounts),
|
||||
"Open Google Password Manager", "elisa.beckett@gmail.com"
|
||||
),
|
||||
newActionEntry(
|
||||
"key3", "subkey-2", TYPE_PASSWORD_CREDENTIAL,
|
||||
Icon.createWithResource(context, R.drawable.ic_manage_accounts),
|
||||
"Open Google Password Manager", "beckett-family@gmail.com"
|
||||
),
|
||||
)
|
||||
@@ -275,7 +273,6 @@ class CredentialManagerRepo(
|
||||
listOf(
|
||||
newActionEntry(
|
||||
"key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL,
|
||||
Icon.createWithResource(context, R.drawable.ic_face),
|
||||
"Open Enpass"
|
||||
),
|
||||
)
|
||||
@@ -287,7 +284,6 @@ class CredentialManagerRepo(
|
||||
key: String,
|
||||
subkey: String,
|
||||
credentialType: String,
|
||||
icon: Icon,
|
||||
text: String,
|
||||
subtext: String? = null,
|
||||
): Entry {
|
||||
@@ -295,7 +291,7 @@ class CredentialManagerRepo(
|
||||
Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
|
||||
).addText(
|
||||
text, null, listOf(Entry.HINT_ACTION_TITLE)
|
||||
).addIcon(icon, null, listOf(Entry.HINT_ACTION_ICON))
|
||||
)
|
||||
if (subtext != null) {
|
||||
slice.addText(subtext, null, listOf(Entry.HINT_ACTION_SUBTEXT))
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@ class GetFlowUtils {
|
||||
.getPackageInfo(packageName!!,
|
||||
PackageManager.PackageInfoFlags.of(0))
|
||||
val providerDisplayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString()
|
||||
// TODO: decide what to do when failed to load a provider icon
|
||||
// TODO: get the provider icon from the service
|
||||
// and decide what to do when failed to load a provider icon
|
||||
val providerIcon = pkgInfo.applicationInfo.loadIcon(packageManager)!!
|
||||
ProviderInfo(
|
||||
id = it.providerFlattenedComponentName,
|
||||
@@ -83,7 +84,7 @@ class GetFlowUtils {
|
||||
it.authenticationEntry),
|
||||
remoteEntry = getRemoteEntry(it.providerFlattenedComponentName, it.remoteEntry),
|
||||
actionEntryList = getActionEntryList(
|
||||
it.providerFlattenedComponentName, it.actionChips, context),
|
||||
it.providerFlattenedComponentName, it.actionChips, providerIcon),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -119,7 +120,7 @@ class GetFlowUtils {
|
||||
displayName = credentialEntryUi.userDisplayName?.toString(),
|
||||
// TODO: proper fallback
|
||||
icon = credentialEntryUi.entryIcon?.loadDrawable(context)
|
||||
?: context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
?: context.getDrawable(R.drawable.ic_other_sign_in)!!,
|
||||
lastUsedTimeMillis = credentialEntryUi.lastUsedTimeMillis,
|
||||
)
|
||||
}
|
||||
@@ -164,7 +165,7 @@ class GetFlowUtils {
|
||||
private fun getActionEntryList(
|
||||
providerId: String,
|
||||
actionEntries: List<Entry>,
|
||||
context: Context,
|
||||
providerIcon: Drawable,
|
||||
): List<ActionEntryInfo> {
|
||||
return actionEntries.map {
|
||||
val actionEntryUi = ActionUi.fromSlice(it.slice)
|
||||
@@ -177,7 +178,7 @@ class GetFlowUtils {
|
||||
fillInIntent = it.frameworkExtrasIntent,
|
||||
title = actionEntryUi.text.toString(),
|
||||
// TODO: gracefully fail
|
||||
icon = actionEntryUi.icon.loadDrawable(context)!!,
|
||||
icon = providerIcon,
|
||||
subTitle = actionEntryUi.subtext?.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.credentialmanager.getflow
|
||||
|
||||
import android.credentials.Credential
|
||||
import android.text.TextUtils
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.result.ActivityResult
|
||||
@@ -35,6 +36,7 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.outlined.Lock
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -189,7 +191,7 @@ fun PrimarySelectionCard(
|
||||
color = Color.Transparent
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
) {
|
||||
CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel)
|
||||
@@ -423,11 +425,12 @@ fun CredentialEntryRow(
|
||||
Entry(
|
||||
onClick = { onEntrySelected(credentialEntryInfo) },
|
||||
icon = {
|
||||
Image(
|
||||
Icon(
|
||||
modifier = Modifier.padding(start = 10.dp).size(32.dp),
|
||||
bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
|
||||
// TODO: add description.
|
||||
contentDescription = ""
|
||||
contentDescription = "",
|
||||
tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
|
||||
)
|
||||
},
|
||||
label = {
|
||||
@@ -436,19 +439,24 @@ fun CredentialEntryRow(
|
||||
TextOnSurfaceVariant(
|
||||
text = credentialEntryInfo.userName,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
modifier = Modifier.padding(top = 16.dp, start = 5.dp)
|
||||
)
|
||||
TextSecondary(
|
||||
text =
|
||||
if (TextUtils.isEmpty(credentialEntryInfo.displayName))
|
||||
credentialEntryInfo.credentialTypeDisplayName
|
||||
else
|
||||
credentialEntryInfo.credentialTypeDisplayName +
|
||||
stringResource(
|
||||
R.string.get_dialog_sign_in_type_username_separator) +
|
||||
credentialEntryInfo.displayName,
|
||||
text = if (
|
||||
credentialEntryInfo.credentialType == Credential.TYPE_PASSWORD_CREDENTIAL) {
|
||||
"••••••••••••"
|
||||
} else {
|
||||
if (TextUtils.isEmpty(credentialEntryInfo.displayName))
|
||||
credentialEntryInfo.credentialTypeDisplayName
|
||||
else
|
||||
credentialEntryInfo.credentialTypeDisplayName +
|
||||
stringResource(
|
||||
R.string.get_dialog_sign_in_type_username_separator
|
||||
) +
|
||||
credentialEntryInfo.displayName
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
modifier = Modifier.padding(bottom = 16.dp, start = 5.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -472,17 +480,27 @@ fun AuthenticationEntryRow(
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Column() {
|
||||
// TODO: fix the text values.
|
||||
TextOnSurfaceVariant(
|
||||
text = authenticationEntryInfo.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
)
|
||||
TextSecondary(
|
||||
text = stringResource(R.string.locked_credential_entry_label_subtext),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp),
|
||||
) {
|
||||
Column() {
|
||||
// TODO: fix the text values.
|
||||
TextOnSurfaceVariant(
|
||||
text = authenticationEntryInfo.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
)
|
||||
TextSecondary(
|
||||
text = stringResource(R.string.locked_credential_entry_label_subtext),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
Icons.Outlined.Lock,
|
||||
null,
|
||||
Modifier.align(alignment = Alignment.CenterVertically).padding(end = 10.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -509,11 +527,13 @@ fun ActionEntryRow(
|
||||
TextOnSurfaceVariant(
|
||||
text = actionEntryInfo.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(start = 5.dp),
|
||||
)
|
||||
if (actionEntryInfo.subTitle != null) {
|
||||
TextSecondary(
|
||||
text = actionEntryInfo.subTitle,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(start = 5.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,16 @@ private fun toGetScreenState(
|
||||
|
||||
internal class CredentialEntryInfoComparator : Comparator<CredentialEntryInfo> {
|
||||
override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
|
||||
// First order by last used timestamp
|
||||
// First prefer passkey type for its security benefits
|
||||
if (p0.credentialType != p1.credentialType) {
|
||||
if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p0.credentialType) {
|
||||
return -1
|
||||
} else if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p1.credentialType) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// Then order by last used timestamp
|
||||
if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) {
|
||||
if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) {
|
||||
return 1
|
||||
@@ -233,15 +242,6 @@ internal class CredentialEntryInfoComparator : Comparator<CredentialEntryInfo> {
|
||||
} else if (p1.lastUsedTimeMillis != null && p1.lastUsedTimeMillis > 0) {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Then prefer passkey type for its security benefits
|
||||
if (p0.credentialType != p1.credentialType) {
|
||||
if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p0.credentialType) {
|
||||
return -1
|
||||
} else if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p1.credentialType) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package com.android.credentialmanager.jetpack.provider
|
||||
|
||||
import android.app.slice.Slice
|
||||
import android.credentials.ui.Entry
|
||||
import android.graphics.drawable.Icon
|
||||
|
||||
/**
|
||||
* UI representation for a credential entry used during the get credential flow.
|
||||
@@ -26,28 +25,24 @@ import android.graphics.drawable.Icon
|
||||
* TODO: move to jetpack.
|
||||
*/
|
||||
class ActionUi(
|
||||
val icon: Icon,
|
||||
val text: CharSequence,
|
||||
val subtext: CharSequence?,
|
||||
) {
|
||||
companion object {
|
||||
fun fromSlice(slice: Slice): ActionUi {
|
||||
var icon: Icon? = null
|
||||
var text: CharSequence? = null
|
||||
var subtext: CharSequence? = null
|
||||
|
||||
val items = slice.items
|
||||
items.forEach {
|
||||
if (it.hasHint(Entry.HINT_ACTION_ICON)) {
|
||||
icon = it.icon
|
||||
} else if (it.hasHint(Entry.HINT_ACTION_TITLE)) {
|
||||
if (it.hasHint(Entry.HINT_ACTION_TITLE)) {
|
||||
text = it.text
|
||||
} else if (it.hasHint(Entry.HINT_ACTION_SUBTEXT)) {
|
||||
subtext = it.text
|
||||
}
|
||||
}
|
||||
// TODO: fail NPE more elegantly.
|
||||
return ActionUi(icon!!, text!!, subtext)
|
||||
return ActionUi(text!!, subtext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user