diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp index 2cb34680845b8..00d42bd836b1d 100644 --- a/packages/CredentialManager/Android.bp +++ b/packages/CredentialManager/Android.bp @@ -31,6 +31,7 @@ android_app { "androidx.compose.ui_ui", "androidx.compose.ui_ui-tooling", "androidx.core_core-ktx", + "androidx.credentials_credentials", "androidx.lifecycle_lifecycle-extensions", "androidx.lifecycle_lifecycle-livedata", "androidx.lifecycle_lifecycle-runtime-ktx", diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 7b53e26caefbd..c66ea5e1af9e0 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -16,12 +16,10 @@ package com.android.credentialmanager -import android.app.PendingIntent import android.app.slice.Slice import android.app.slice.SliceSpec import android.content.Context import android.content.Intent -import android.content.pm.SigningInfo import android.credentials.CreateCredentialRequest import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL import android.credentials.CredentialOption @@ -40,18 +38,15 @@ import android.net.Uri import android.os.Binder import android.os.Bundle import android.os.ResultReceiver -import android.service.credentials.CredentialProviderService import com.android.credentialmanager.createflow.DisabledProviderInfo import com.android.credentialmanager.createflow.EnabledProviderInfo import com.android.credentialmanager.createflow.RequestDisplayInfo import com.android.credentialmanager.getflow.GetCredentialUiState -import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toCredentialDataBundle -import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest -import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL -import com.android.credentialmanager.jetpack.provider.Action -import com.android.credentialmanager.jetpack.provider.CreateEntry -import com.android.credentialmanager.jetpack.provider.CredentialCountInformation -import com.android.credentialmanager.jetpack.provider.CredentialEntry +import androidx.credentials.CreateCredentialRequest.DisplayInfo +import androidx.credentials.CreatePublicKeyCredentialRequest +import androidx.credentials.CreatePasswordRequest + +import java.time.Instant // Consider repo per screen, similar to view model? class CredentialManagerRepo( @@ -180,14 +175,14 @@ class CredentialManagerRepo( .Builder("io.enpass.app") .setSaveEntries( listOf( - newCreateEntry( + CreateTestUtils.newCreateEntry(context, "key1", "subkey-1", "elisa.beckett@gmail.com", - 20, 7, 27, 10L, - "Optional footer description" + 20, 7, 27, Instant.ofEpochSecond(10L), + "Legal note" ), - newCreateEntry( + CreateTestUtils.newCreateEntry(context, "key1", "subkey-2", "elisa.work@google.com", - 20, 7, 27, 12L, + 20, 7, 27, Instant.ofEpochSecond(12L), null ), ) @@ -200,14 +195,14 @@ class CredentialManagerRepo( .Builder("com.dashlane") .setSaveEntries( listOf( - newCreateEntry( + CreateTestUtils.newCreateEntry(context, "key1", "subkey-3", "elisa.beckett@dashlane.com", - 20, 7, 27, 11L, + 20, 7, 27, Instant.ofEpochSecond(11L), null ), - newCreateEntry( + CreateTestUtils.newCreateEntry(context, "key1", "subkey-4", "elisa.work@dashlane.com", - 20, 7, 27, 14L, + 20, 7, 27, Instant.ofEpochSecond(14L), null ), ) @@ -228,33 +223,33 @@ class CredentialManagerRepo( GetCredentialProviderData.Builder("io.enpass.app") .setCredentialEntries( listOf( - newGetEntry( - "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password", - "elisa.family@outlook.com", null, 3L + GetTestUtils.newPasswordEntry( + context, "key1", "subkey-1", "elisa.family@outlook.com", null, + Instant.ofEpochSecond(8000L) ), - newGetEntry( - "key1", "subkey-1", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey", - "elisa.bakery@gmail.com", "Elisa Beckett", 0L + GetTestUtils.newPasskeyEntry( + context, "key1", "subkey-1", "elisa.bakery@gmail.com", "Elisa Beckett", + null ), - newGetEntry( - "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password", - "elisa.bakery@gmail.com", null, 10L + GetTestUtils.newPasswordEntry( + context, "key1", "subkey-2", "elisa.bakery@gmail.com", null, + Instant.ofEpochSecond(10000L) ), - newGetEntry( - "key1", "subkey-3", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey", - "elisa.family@outlook.com", "Elisa Beckett", 1L + GetTestUtils.newPasskeyEntry( + context, "key1", "subkey-3", "elisa.family@outlook.com", + "Elisa Beckett", Instant.ofEpochSecond(500L) ), ) ).setAuthenticationEntry( - newAuthenticationEntry("key2", "subkey-1", TYPE_PASSWORD_CREDENTIAL) + GetTestUtils.newAuthenticationEntry(context, "key2", "subkey-1") ).setActionChips( listOf( - newActionEntry( - "key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL, + GetTestUtils.newActionEntry( + context, "key3", "subkey-1", "Open Google Password Manager", "elisa.beckett@gmail.com" ), - newActionEntry( - "key3", "subkey-2", TYPE_PASSWORD_CREDENTIAL, + GetTestUtils.newActionEntry( + context, "key3", "subkey-2", "Open Google Password Manager", "beckett-family@gmail.com" ), ) @@ -264,137 +259,29 @@ class CredentialManagerRepo( GetCredentialProviderData.Builder("com.dashlane") .setCredentialEntries( listOf( - newGetEntry( - "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password", - "elisa.family@outlook.com", null, 4L + GetTestUtils.newPasswordEntry( + context, "key1", "subkey-2", "elisa.family@outlook.com", null, + Instant.ofEpochSecond(9000L) ), - newGetEntry( - "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password", - "elisa.work@outlook.com", null, 11L + GetTestUtils.newPasswordEntry( + context, "key1", "subkey-3", "elisa.work@outlook.com", null, + Instant.ofEpochSecond(11000L) ), ) ).setAuthenticationEntry( - newAuthenticationEntry("key2", "subkey-1", TYPE_PASSWORD_CREDENTIAL) + GetTestUtils.newAuthenticationEntry(context, "key2", "subkey-1") ).setActionChips( listOf( - newActionEntry( - "key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL, - "Open Enpass" + GetTestUtils.newActionEntry( + context, "key3", "subkey-1", "Open Enpass", + "Manage passwords" ), ) ).build(), ) } - private fun newActionEntry( - key: String, - subkey: String, - credentialType: String, - text: String, - subtext: String? = null, - ): Entry { - val action = Action(text, subtext, null) - return Entry( - key, - subkey, - Action.toSlice(action) - ) - } - - private fun newAuthenticationEntry( - key: String, - subkey: String, - credentialType: String, - ): Entry { - val slice = Slice.Builder( - Uri.EMPTY, SliceSpec(credentialType, 1) - ) - return Entry( - key, - subkey, - slice.build() - ) - } - - private fun newGetEntry( - key: String, - subkey: String, - credentialType: String, - credentialTypeDisplayName: String, - userName: String, - userDisplayName: String?, - lastUsedTimeMillis: Long?, - ): Entry { - val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD") - .setPackage("com.androidauth.androidvault") - intent.putExtra("provider_extra_sample", "testprovider") - - val pendingIntent = PendingIntent.getActivity( - context, 1, - intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - or PendingIntent.FLAG_ONE_SHOT) - ) - - val credentialEntry = CredentialEntry( - credentialType, credentialTypeDisplayName, userName, - userDisplayName, pendingIntent, lastUsedTimeMillis - ?: 0L, null, false - ) - - return Entry( - key, - subkey, - CredentialEntry.toSlice(credentialEntry), - Intent() - ) - } - - private fun newCreateEntry( - key: String, - subkey: String, - providerDisplayName: String, - passwordCount: Int, - passkeyCount: Int, - totalCredentialCount: Int, - lastUsedTimeMillis: Long, - footerDescription: String?, - ): Entry { - val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD") - .setPackage("com.androidauth.androidvault") - intent.putExtra("provider_extra_sample", "testprovider") - val pendingIntent = PendingIntent.getActivity( - context, 1, - intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - or PendingIntent.FLAG_ONE_SHOT) - ) - val createPasswordRequest = android.service.credentials.CreateCredentialRequest( - android.service.credentials.CallingAppInfo( - context.applicationInfo.packageName, SigningInfo() - ), - TYPE_PASSWORD_CREDENTIAL, - toCredentialDataBundle("beckett-bakert@gmail.com", "password123") - ) - val fillInIntent = Intent().putExtra( - CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST, - createPasswordRequest - ) - - val createEntry = CreateEntry( - providerDisplayName, pendingIntent, - null, lastUsedTimeMillis, - listOf( - CredentialCountInformation.createPasswordCountInformation(passwordCount), - CredentialCountInformation.createPublicKeyCountInformation(passkeyCount), - ), footerDescription - ) - return Entry( - key, - subkey, - CreateEntry.toSlice(createEntry), - fillInIntent - ) - } private fun newRemoteEntry( key: String, @@ -451,7 +338,7 @@ class CredentialManagerRepo( return RequestInfo.newCreateRequestInfo( Binder(), CreateCredentialRequest( - TYPE_PUBLIC_KEY_CREDENTIAL, + "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL", credentialData, // TODO: populate with actual data /*candidateQueryData=*/ Bundle(), @@ -462,14 +349,13 @@ class CredentialManagerRepo( } private fun testCreatePasswordRequestInfo(): RequestInfo { - val data = toCredentialDataBundle("beckett-bakert@gmail.com", "password123") + val request = CreatePasswordRequest("beckett-bakert@gmail.com", "password123") return RequestInfo.newCreateRequestInfo( Binder(), CreateCredentialRequest( TYPE_PASSWORD_CREDENTIAL, - data, - // TODO: populate with actual data - /*candidateQueryData=*/ Bundle(), + request.credentialData, + request.candidateQueryData, /*isSystemProviderRequired=*/ false ), "com.google.android.youtube" @@ -478,6 +364,10 @@ class CredentialManagerRepo( private fun testCreateOtherCredentialRequestInfo(): RequestInfo { val data = Bundle() + val displayInfo = DisplayInfo("my-username00", "Joe") + data.putBundle( + "androidx.credentials.BUNDLE_KEY_REQUEST_DISPLAY_INFO", + displayInfo.toBundle()) return RequestInfo.newCreateRequestInfo( Binder(), CreateCredentialRequest( @@ -498,7 +388,7 @@ class CredentialManagerRepo( ) .addCredentialOption( CredentialOption( - TYPE_PUBLIC_KEY_CREDENTIAL, + "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL", Bundle(), Bundle(), /*isSystemProviderRequired=*/ false diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 3f705d648ec47..d8420cdd7ff1a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -16,18 +16,22 @@ package com.android.credentialmanager +import android.app.slice.Slice import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager +import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL import android.credentials.ui.CreateCredentialProviderData import android.credentials.ui.DisabledProviderData import android.credentials.ui.Entry import android.credentials.ui.GetCredentialProviderData import android.credentials.ui.RequestInfo import android.graphics.drawable.Drawable +import android.service.credentials.CredentialEntry import android.text.TextUtils import android.util.Log import com.android.credentialmanager.common.Constants +import com.android.credentialmanager.common.CredentialType import com.android.credentialmanager.createflow.ActiveEntry import com.android.credentialmanager.createflow.CreateCredentialUiState import com.android.credentialmanager.createflow.CreateOptionInfo @@ -41,17 +45,22 @@ import com.android.credentialmanager.getflow.AuthenticationEntryInfo import com.android.credentialmanager.getflow.CredentialEntryInfo import com.android.credentialmanager.getflow.ProviderInfo import com.android.credentialmanager.getflow.RemoteEntryInfo -import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest -import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest -import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest -import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL -import com.android.credentialmanager.jetpack.provider.Action -import com.android.credentialmanager.jetpack.provider.AuthenticationAction -import com.android.credentialmanager.jetpack.provider.CreateEntry -import com.android.credentialmanager.jetpack.provider.CredentialCountInformation -import com.android.credentialmanager.jetpack.provider.CredentialEntry +import androidx.credentials.CreateCredentialRequest +import androidx.credentials.CreateCustomCredentialRequest +import androidx.credentials.CreatePasswordRequest +import androidx.credentials.CreatePublicKeyCredentialRequest +import androidx.credentials.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL +import androidx.credentials.provider.Action +import androidx.credentials.provider.AuthenticationAction +import androidx.credentials.provider.CreateEntry +import androidx.credentials.provider.CustomCredentialEntry +import androidx.credentials.provider.PasswordCredentialEntry +import androidx.credentials.provider.PublicKeyCredentialEntry +import androidx.credentials.provider.RemoteCreateEntry +import androidx.credentials.provider.RemoteCredentialEntry import org.json.JSONObject +// TODO: remove all !! checks private fun getAppLabel( pm: PackageManager, appPackageName: String @@ -174,25 +183,76 @@ class GetFlowUtils { credentialEntries: List, context: Context, ): List { - return credentialEntries.map { - // TODO: handle NPE gracefully - val credentialEntry = CredentialEntry.fromSlice(it.slice)!! + val result: MutableList = mutableListOf() + credentialEntries.forEach { + val credentialEntry = parseCredentialEntryFromSlice(it.slice) + when (credentialEntry) { + is PasswordCredentialEntry -> { + result.add(CredentialEntryInfo( + providerId = providerId, + entryKey = it.key, + entrySubkey = it.subkey, + pendingIntent = credentialEntry.pendingIntent, + fillInIntent = it.frameworkExtrasIntent, + credentialType = CredentialType.PASSWORD, + credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), + userName = credentialEntry.username.toString(), + displayName = credentialEntry.displayName?.toString(), + icon = credentialEntry.icon.loadDrawable(context), + lastUsedTimeMillis = credentialEntry.lastUsedTime, + )) + } + is PublicKeyCredentialEntry -> { + result.add(CredentialEntryInfo( + providerId = providerId, + entryKey = it.key, + entrySubkey = it.subkey, + pendingIntent = credentialEntry.pendingIntent, + fillInIntent = it.frameworkExtrasIntent, + credentialType = CredentialType.PASSKEY, + credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), + userName = credentialEntry.username.toString(), + displayName = credentialEntry.displayName?.toString(), + icon = credentialEntry.icon.loadDrawable(context), + lastUsedTimeMillis = credentialEntry.lastUsedTime, + )) + } + is CustomCredentialEntry -> { + result.add(CredentialEntryInfo( + providerId = providerId, + entryKey = it.key, + entrySubkey = it.subkey, + pendingIntent = credentialEntry.pendingIntent, + fillInIntent = it.frameworkExtrasIntent, + credentialType = CredentialType.UNKNOWN, + credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), + userName = credentialEntry.title.toString(), + displayName = credentialEntry.subtitle?.toString(), + icon = credentialEntry.icon.loadDrawable(context), + lastUsedTimeMillis = credentialEntry.lastUsedTime, + )) + } + else -> Log.d( + Constants.LOG_TAG, + "Encountered unrecognized credential entry ${it.slice.spec?.type}" + ) + } + } + // TODO: handle empty list due to parsing error. + return result + } - // Consider directly move the UI object into the class. - return@map CredentialEntryInfo( - providerId = providerId, - entryKey = it.key, - entrySubkey = it.subkey, - pendingIntent = credentialEntry.pendingIntent, - fillInIntent = it.frameworkExtrasIntent, - credentialType = credentialEntry.type, - credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), - userName = credentialEntry.username.toString(), - displayName = credentialEntry.displayName?.toString(), - // TODO: proper fallback - icon = credentialEntry.icon?.loadDrawable(context), - lastUsedTimeMillis = credentialEntry.lastUsedTimeMillis, - ) + private fun parseCredentialEntryFromSlice(slice: Slice): CredentialEntry? { + try { + when (slice.spec?.type) { + TYPE_PASSWORD_CREDENTIAL -> return PasswordCredentialEntry.fromSlice(slice)!! + TYPE_PUBLIC_KEY_CREDENTIAL -> return PublicKeyCredentialEntry.fromSlice(slice)!! + else -> return CustomCredentialEntry.fromSlice(slice)!! + } + } catch (e: Exception) { + // Try CustomCredentialEntry.fromSlice one last time in case the cause was a failed + // password / passkey parsing attempt. + return CustomCredentialEntry.fromSlice(slice) } } @@ -205,18 +265,13 @@ class GetFlowUtils { if (authEntry == null) { return null } - val authStructuredEntry = AuthenticationAction.fromSlice( - authEntry!!.slice - ) - if (authStructuredEntry == null) { - return null - } - + val structuredAuthEntry = + AuthenticationAction.fromSlice(authEntry.slice) ?: return null return AuthenticationEntryInfo( providerId = providerId, entryKey = authEntry.key, entrySubkey = authEntry.subkey, - pendingIntent = authStructuredEntry.pendingIntent, + pendingIntent = structuredAuthEntry.pendingIntent, fillInIntent = authEntry.frameworkExtrasIntent, title = providerDisplayName, icon = providerIcon, @@ -228,11 +283,13 @@ class GetFlowUtils { if (remoteEntry == null) { return null } + val structuredRemoteEntry = RemoteCredentialEntry.fromSlice(remoteEntry.slice) + ?: return null return RemoteEntryInfo( providerId = providerId, entryKey = remoteEntry.key, entrySubkey = remoteEntry.subkey, - pendingIntent = remoteEntry.pendingIntent, + pendingIntent = structuredRemoteEntry.pendingIntent, fillInIntent = remoteEntry.frameworkExtrasIntent, ) } @@ -242,22 +299,22 @@ class GetFlowUtils { actionEntries: List, providerIcon: Drawable, ): List { - return actionEntries.map { - // TODO: handle NPE gracefully - val actionEntryUi = Action.fromSlice(it.slice)!! - - return@map ActionEntryInfo( + val result: MutableList = mutableListOf() + actionEntries.forEach { + val actionEntryUi = Action.fromSlice(it.slice) ?: return@forEach + result.add(ActionEntryInfo( providerId = providerId, entryKey = it.key, entrySubkey = it.subkey, pendingIntent = actionEntryUi.pendingIntent, fillInIntent = it.frameworkExtrasIntent, title = actionEntryUi.title.toString(), - // TODO: gracefully fail icon = providerIcon, - subTitle = actionEntryUi.subTitle?.toString(), - ) + subTitle = actionEntryUi.subtitle?.toString(), + )) } + // TODO: handle empty list + return result } } } @@ -316,22 +373,21 @@ class CreateFlowUtils { ): RequestDisplayInfo? { val appLabel = getAppLabel(context.packageManager, requestInfo.appPackageName) ?: return null - val createCredentialRequest = requestInfo.createCredentialRequest - val createCredentialRequestJetpack = createCredentialRequest?.let { - CreateCredentialRequest.createFrom( - it.type, it.credentialData, it.candidateQueryData, it.isSystemProviderRequired + val createCredentialRequest = requestInfo.createCredentialRequest ?: return null + val createCredentialRequestJetpack = CreateCredentialRequest.createFrom( + createCredentialRequest.type, + createCredentialRequest.credentialData, + createCredentialRequest.candidateQueryData, + createCredentialRequest.isSystemProviderRequired + ) + return when (createCredentialRequestJetpack) { + is CreatePasswordRequest -> RequestDisplayInfo( + createCredentialRequestJetpack.id, + createCredentialRequestJetpack.password, + CredentialType.PASSWORD, + appLabel, + context.getDrawable(R.drawable.ic_password)!! ) - } - when (createCredentialRequestJetpack) { - is CreatePasswordRequest -> { - return RequestDisplayInfo( - createCredentialRequestJetpack.id, - createCredentialRequestJetpack.password, - createCredentialRequestJetpack.type, - appLabel, - context.getDrawable(R.drawable.ic_password)!! - ) - } is CreatePublicKeyCredentialRequest -> { val requestJson = createCredentialRequestJetpack.requestJson val json = JSONObject(requestJson) @@ -342,24 +398,29 @@ class CreateFlowUtils { name = user.getString("name") displayName = user.getString("displayName") } - return RequestDisplayInfo( + RequestDisplayInfo( name, displayName, - createCredentialRequestJetpack.type, + CredentialType.PASSKEY, appLabel, context.getDrawable(R.drawable.ic_passkey)!! ) } - // TODO: correctly parsing for other sign-ins - else -> { - return RequestDisplayInfo( - "beckett-bakert@gmail.com", - "Elisa Beckett", - "other-sign-ins", - appLabel.toString(), - context.getDrawable(R.drawable.ic_other_sign_in)!! + is CreateCustomCredentialRequest -> { + // TODO: directly use the display info once made public + val displayInfo = CreateCredentialRequest.DisplayInfo + .parseFromCredentialDataBundle(createCredentialRequest.credentialData) + ?: return null + RequestDisplayInfo( + title = displayInfo.userId, + subtitle = displayInfo.userDisplayName, + type = CredentialType.UNKNOWN, + appName = appLabel, + typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context) + ?: context.getDrawable(R.drawable.ic_other_sign_in)!! ) } + else -> null } } @@ -408,7 +469,7 @@ class CreateFlowUtils { currentScreenState = initialScreenState, requestDisplayInfo = requestDisplayInfo, sortedCreateOptionsPairs = createOptionsPairs.sortedWith( - compareByDescending { it.first.lastUsedTimeMillis } + compareByDescending { it.first.lastUsedTime } ), hasDefaultProvider = defaultProvider != null, activeEntry = toActiveEntry( @@ -430,7 +491,7 @@ class CreateFlowUtils { isPasskeyFirstUse: Boolean, ): CreateScreenState? { return if (isPasskeyFirstUse && requestDisplayInfo.type == - TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) { + CredentialType.PASSKEY && !isOnPasskeyIntroStateAlready) { CreateScreenState.PASSKEY_INTRO } else if ((defaultProvider == null || defaultProvider.createOptions.isEmpty()) && createOptionSize > 1) { @@ -479,12 +540,10 @@ class CreateFlowUtils { creationEntries: List, context: Context, ): List { - return creationEntries.map { - // TODO: handle NPE gracefully - val createEntry = CreateEntry.fromSlice(it.slice)!! - - return@map CreateOptionInfo( - // TODO: remove fallbacks + val result: MutableList = mutableListOf() + creationEntries.forEach { + val createEntry = CreateEntry.fromSlice(it.slice) ?: return@forEach + result.add(CreateOptionInfo( providerId = providerId, entryKey = it.key, entrySubkey = it.subkey, @@ -492,32 +551,28 @@ class CreateFlowUtils { fillInIntent = it.frameworkExtrasIntent, userProviderDisplayName = createEntry.accountName.toString(), profileIcon = createEntry.icon?.loadDrawable(context), - passwordCount = CredentialCountInformation.getPasswordCount( - createEntry.credentialCountInformationList - ) ?: 0, - passkeyCount = CredentialCountInformation.getPasskeyCount( - createEntry.credentialCountInformationList - ) ?: 0, - totalCredentialCount = CredentialCountInformation.getTotalCount( - createEntry.credentialCountInformationList - ) ?: 0, - lastUsedTimeMillis = createEntry.lastUsedTimeMillis ?: 0, - footerDescription = createEntry.footerDescription?.toString() - ) + passwordCount = createEntry.getPasswordCredentialCount(), + passkeyCount = createEntry.getPublicKeyCredentialCount(), + totalCredentialCount = createEntry.getTotalCredentialCount(), + lastUsedTime = createEntry.lastUsedTime, + footerDescription = createEntry.description?.toString() + )) } + return result } private fun toRemoteInfo( providerId: String, remoteEntry: Entry?, ): RemoteInfo? { - // TODO: should also call fromSlice after getting the official jetpack code. return if (remoteEntry != null) { + val structuredRemoteEntry = RemoteCreateEntry.fromSlice(remoteEntry.slice) + ?: return null RemoteInfo( providerId = providerId, entryKey = remoteEntry.key, entrySubkey = remoteEntry.subkey, - pendingIntent = remoteEntry.pendingIntent, + pendingIntent = structuredRemoteEntry.pendingIntent, fillInIntent = remoteEntry.frameworkExtrasIntent, ) } else null diff --git a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt new file mode 100644 index 0000000000000..e3bbaebfa16fd --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt @@ -0,0 +1,352 @@ +/* + * 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 + +import android.app.PendingIntent +import android.app.slice.Slice +import android.app.slice.SliceSpec +import android.content.Context +import android.content.Intent +import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL +import android.credentials.ui.Entry +import android.graphics.drawable.Icon +import android.net.Uri +import android.provider.Settings +import androidx.credentials.provider.CreateEntry + +import java.time.Instant + +// TODO: remove once testing is complete +class GetTestUtils { + companion object { + internal fun newAuthenticationEntry( + context: Context, + key: String, + subkey: String, + ): Entry { + val slice = Slice.Builder( + Uri.EMPTY, SliceSpec("AuthenticationAction", 0) + ) + val intent = Intent(Settings.ACTION_SYNC_SETTINGS) + val pendingIntent = + PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + slice.addAction( + pendingIntent, + Slice.Builder(slice) + .addHints(listOf("androidx.credentials.provider.authenticationAction" + + ".SLICE_HINT_PENDING_INTENT")) + .build(), + /*subType=*/null + ) + return Entry( + key, + subkey, + slice.build() + ) + } + + internal fun newActionEntry( + context: Context, + key: String, + subkey: String, + text: String, + subtext: String? = null, + ): Entry { + val intent = Intent(Settings.ACTION_SYNC_SETTINGS) + val pendingIntent = + PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("Action", 0)) + .addText( + text, /*subType=*/null, + listOf("androidx.credentials.provider.action.HINT_ACTION_TITLE") + ) + .addText( + subtext, /*subType=*/null, + listOf("androidx.credentials.provider.action.HINT_ACTION_SUBTEXT") + ) + sliceBuilder.addAction( + pendingIntent, + Slice.Builder(sliceBuilder) + .addHints( + listOf("androidx.credentials.provider.action.SLICE_HINT_PENDING_INTENT") + ) + .build(), + /*subType=*/null + ) + return Entry( + key, + subkey, + sliceBuilder.build() + ) + } + + private const val SLICE_HINT_TYPE_DISPLAY_NAME = + "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME" + private const val SLICE_HINT_TITLE = + "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_USER_NAME" + private const val SLICE_HINT_SUBTITLE = + "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME" + private const val SLICE_HINT_LAST_USED_TIME_MILLIS = + "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS" + private const val SLICE_HINT_ICON = + "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_PROFILE_ICON" + private const val SLICE_HINT_PENDING_INTENT = + "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_PENDING_INTENT" + private const val SLICE_HINT_AUTO_ALLOWED = + "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_AUTO_ALLOWED" + private const val AUTO_SELECT_TRUE_STRING = "true" + private const val AUTO_SELECT_FALSE_STRING = "false" + + internal fun newPasswordEntry( + context: Context, + key: String, + subkey: String, + userName: String, + userDisplayName: String?, + lastUsedTime: Instant?, + ): Entry { + val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD") + .setPackage("com.androidauth.androidvault") + intent.putExtra("provider_extra_sample", "testprovider") + val pendingIntent = PendingIntent.getActivity( + context, 1, + intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + or PendingIntent.FLAG_ONE_SHOT) + ) + return Entry( + key, + subkey, + toPasswordSlice(userName, userDisplayName, pendingIntent, lastUsedTime), + Intent() + ) + } + + private fun toPasswordSlice( + title: CharSequence, + subTitle: CharSequence?, + pendingIntent: PendingIntent, + lastUsedTime: Instant?, + icon: Icon? = null, + isAutoSelectAllowed: Boolean = true + ): Slice { + val type = TYPE_PASSWORD_CREDENTIAL + val autoSelectAllowed = if (isAutoSelectAllowed) { + AUTO_SELECT_TRUE_STRING + } else { + AUTO_SELECT_FALSE_STRING + } + val sliceBuilder = Slice.Builder( + Uri.EMPTY, SliceSpec( + type, 1 + ) + ) + .addText( + "Password", /*subType=*/null, + listOf(SLICE_HINT_TYPE_DISPLAY_NAME) + ) + .addText( + title, /*subType=*/null, + listOf(SLICE_HINT_TITLE) + ) + .addText( + subTitle, /*subType=*/null, + listOf(SLICE_HINT_SUBTITLE) + ) + .addText( + autoSelectAllowed, /*subType=*/null, + listOf(SLICE_HINT_AUTO_ALLOWED) + ) + if (lastUsedTime != null) { + sliceBuilder.addLong( + lastUsedTime.toEpochMilli(), + /*subType=*/null, + listOf(SLICE_HINT_LAST_USED_TIME_MILLIS) + ) + } + if (icon != null) { + sliceBuilder.addIcon( + icon, /*subType=*/null, + listOf(SLICE_HINT_ICON) + ) + } + sliceBuilder.addAction( + pendingIntent, + Slice.Builder(sliceBuilder) + .addHints(listOf(SLICE_HINT_PENDING_INTENT)) + .build(), + /*subType=*/null + ) + return sliceBuilder.build() + } + + + private const val PASSKEY_SLICE_HINT_TYPE_DISPLAY_NAME = + "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_TYPE_DISPLAY_NAME" + private const val PASSKEY_SLICE_HINT_TITLE = + "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_USER_NAME" + private const val PASSKEY_SLICE_HINT_SUBTITLE = + "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_TYPE_DISPLAY_NAME" + private const val PASSKEY_SLICE_HINT_LAST_USED_TIME_MILLIS = + "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_LAST_USED_TIME_MILLIS" + private const val PASSKEY_SLICE_HINT_ICON = + "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_PROFILE_ICON" + private const val PASSKEY_SLICE_HINT_PENDING_INTENT = + "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_PENDING_INTENT" + private const val PASSKEY_SLICE_HINT_AUTO_ALLOWED = + "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_AUTO_ALLOWED" + + internal fun newPasskeyEntry( + context: Context, + key: String, + subkey: String, + userName: String, + userDisplayName: String?, + lastUsedTime: Instant?, + ): Entry { + val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD") + .setPackage("com.androidauth.androidvault") + intent.putExtra("provider_extra_sample", "testprovider") + val pendingIntent = PendingIntent.getActivity( + context, 1, + intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + or PendingIntent.FLAG_ONE_SHOT) + ) + return Entry( + key, subkey, toPasskeySlice( + userName, userDisplayName, pendingIntent, lastUsedTime + ), + Intent() + ) + } + + private fun toPasskeySlice( + title: CharSequence, + subTitle: CharSequence?, + pendingIntent: PendingIntent, + lastUsedTime: Instant?, + icon: Icon? = null, + isAutoSelectAllowed: Boolean = true + ): Slice { + val type = "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" + val autoSelectAllowed = if (isAutoSelectAllowed) { + AUTO_SELECT_TRUE_STRING + } else { + AUTO_SELECT_FALSE_STRING + } + val sliceBuilder = Slice.Builder( + Uri.EMPTY, SliceSpec( + type, 1 + ) + ) + .addText( + "Passkey", /*subType=*/null, + listOf(PASSKEY_SLICE_HINT_TYPE_DISPLAY_NAME) + ) + .addText( + title, /*subType=*/null, + listOf(PASSKEY_SLICE_HINT_TITLE) + ) + .addText( + subTitle, /*subType=*/null, + listOf(PASSKEY_SLICE_HINT_SUBTITLE) + ) + .addText( + autoSelectAllowed, /*subType=*/null, + listOf(PASSKEY_SLICE_HINT_AUTO_ALLOWED) + ) + if (lastUsedTime != null) { + sliceBuilder.addLong( + lastUsedTime.toEpochMilli(), + /*subType=*/null, + listOf(PASSKEY_SLICE_HINT_LAST_USED_TIME_MILLIS) + ) + } + if (icon != null) { + sliceBuilder.addIcon( + icon, /*subType=*/null, + listOf(PASSKEY_SLICE_HINT_ICON) + ) + } + sliceBuilder.addAction( + pendingIntent, + Slice.Builder(sliceBuilder) + .addHints(listOf(PASSKEY_SLICE_HINT_PENDING_INTENT)) + .build(), + /*subType=*/null + ) + return sliceBuilder.build() + } + } +} + +class CreateTestUtils { + companion object { + private const val TYPE_TOTAL_CREDENTIAL = "TOTAL_CREDENTIAL_COUNT_TYPE" + private const val SLICE_HINT_ACCOUNT_NAME = + "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME" + private const val SLICE_HINT_NOTE = + "androidx.credentials.provider.createEntry.SLICE_HINT_NOTE" + private const val SLICE_HINT_ICON = + "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON" + private const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION = + "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION" + private const val SLICE_HINT_LAST_USED_TIME_MILLIS = + "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS" + private const val SLICE_HINT_PENDING_INTENT = + "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT" + + internal fun newCreateEntry( + context: Context, + key: String, + subkey: String, + providerUserDisplayName: String, + passwordCount: Int?, + passkeyCount: Int?, + totalCredentialCount: Int?, + lastUsedTime: Instant?, + footerDescription: String?, + ): Entry { + val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD") + .setPackage("com.androidauth.androidvault") + intent.putExtra("provider_extra_sample", "testprovider") + val pendingIntent = PendingIntent.getActivity( + context, 1, + intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + or PendingIntent.FLAG_ONE_SHOT) + ) + val credCountMap = mutableMapOf() + passwordCount?.let { credCountMap.put(TYPE_PASSWORD_CREDENTIAL, it) } + passkeyCount?.let { + credCountMap.put("androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL", it) + } + totalCredentialCount?.let { credCountMap.put(TYPE_TOTAL_CREDENTIAL, it) } + return Entry( + key, + subkey, + CreateEntry.toSlice( + providerUserDisplayName, + null, + footerDescription, + lastUsedTime, + credCountMap, + pendingIntent + ), + Intent() + ) + } + } +} \ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt similarity index 65% rename from packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt rename to packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt index 497c272750acb..cc92f60de048a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -14,12 +14,8 @@ * limitations under the License. */ -package com.android.credentialmanager.jetpack.developer +package com.android.credentialmanager.common -/** - * Internal exception used to indicate a parsing error while converting from a framework type to - * a jetpack type. - * - * @hide - */ -internal class FrameworkClassParsingException : Exception() \ No newline at end of file +enum class CredentialType { + UNKNOWN, PASSKEY, PASSWORD, +} \ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 5e432b9a5edfe..f8d008e3e4afc 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -2,7 +2,6 @@ package com.android.credentialmanager.createflow -import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult import androidx.activity.result.IntentSenderRequest @@ -37,6 +36,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.R +import com.android.credentialmanager.common.CredentialType import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.material.ModalBottomSheetLayout import com.android.credentialmanager.common.material.ModalBottomSheetValue @@ -50,7 +50,6 @@ import com.android.credentialmanager.common.ui.TextOnSurfaceVariant import com.android.credentialmanager.common.ui.ContainerCard import com.android.credentialmanager.ui.theme.EntryShape import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme -import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -288,11 +287,11 @@ fun ProviderSelectionCard( text = stringResource( R.string.choose_provider_title, when (requestDisplayInfo.type) { - TYPE_PUBLIC_KEY_CREDENTIAL -> + CredentialType.PASSKEY -> stringResource(R.string.passkeys) - TYPE_PASSWORD_CREDENTIAL -> + CredentialType.PASSWORD -> stringResource(R.string.passwords) - else -> stringResource(R.string.sign_in_info) + CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) }), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(horizontal = 24.dp) @@ -398,11 +397,11 @@ fun MoreOptionsSelectionCard( stringResource( R.string.save_credential_to_title, when (requestDisplayInfo.type) { - TYPE_PUBLIC_KEY_CREDENTIAL -> + CredentialType.PASSKEY -> stringResource(R.string.passkey) - TYPE_PASSWORD_CREDENTIAL -> + CredentialType.PASSWORD -> stringResource(R.string.password) - else -> stringResource(R.string.sign_in_info) + CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) }), style = MaterialTheme.typography.titleMedium, ) @@ -571,15 +570,15 @@ fun CreationSelectionCard( ) TextOnSurface( text = when (requestDisplayInfo.type) { - TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource( + CredentialType.PASSKEY -> stringResource( R.string.choose_create_option_passkey_title, requestDisplayInfo.appName ) - TYPE_PASSWORD_CREDENTIAL -> stringResource( + CredentialType.PASSWORD -> stringResource( R.string.choose_create_option_password_title, requestDisplayInfo.appName ) - else -> stringResource( + CredentialType.UNKNOWN -> stringResource( R.string.choose_create_option_sign_in_title, requestDisplayInfo.appName ) @@ -828,7 +827,7 @@ fun PrimaryCreateOptionRow( Column() { // TODO: Add the function to hide/view password when the type is create password when (requestDisplayInfo.type) { - TYPE_PUBLIC_KEY_CREDENTIAL -> { + CredentialType.PASSKEY -> { TextOnSurfaceVariant( text = requestDisplayInfo.title, style = MaterialTheme.typography.titleLarge, @@ -846,7 +845,7 @@ fun PrimaryCreateOptionRow( modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), ) } - TYPE_PASSWORD_CREDENTIAL -> { + CredentialType.PASSWORD -> { TextOnSurfaceVariant( text = requestDisplayInfo.title, style = MaterialTheme.typography.titleLarge, @@ -859,7 +858,7 @@ fun PrimaryCreateOptionRow( modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), ) } - else -> { + CredentialType.UNKNOWN -> { if (requestDisplayInfo.subtitle != null) { TextOnSurfaceVariant( text = requestDisplayInfo.title, @@ -917,8 +916,8 @@ fun MoreOptionsInfoRow( modifier = Modifier.padding(start = 5.dp), ) } - if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL || - requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) { + if (requestDisplayInfo.type == CredentialType.PASSKEY || + requestDisplayInfo.type == CredentialType.PASSWORD) { if (createOptionInfo.passwordCount != null && createOptionInfo.passkeyCount != null ) { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 12a5085d44bd8..05be0a65ce13f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -21,6 +21,8 @@ import android.content.Intent import android.graphics.drawable.Drawable import com.android.credentialmanager.common.DialogState import com.android.credentialmanager.common.ProviderActivityState +import com.android.credentialmanager.common.CredentialType +import java.time.Instant data class CreateCredentialUiState( val enabledProviders: List, @@ -68,18 +70,18 @@ open class EntryInfo ( ) class CreateOptionInfo( - providerId: String, - entryKey: String, - entrySubkey: String, - pendingIntent: PendingIntent?, - fillInIntent: Intent?, - val userProviderDisplayName: String?, - val profileIcon: Drawable?, - val passwordCount: Int?, - val passkeyCount: Int?, - val totalCredentialCount: Int?, - val lastUsedTimeMillis: Long?, - val footerDescription: String?, + providerId: String, + entryKey: String, + entrySubkey: String, + pendingIntent: PendingIntent?, + fillInIntent: Intent?, + val userProviderDisplayName: String?, + val profileIcon: Drawable?, + val passwordCount: Int?, + val passkeyCount: Int?, + val totalCredentialCount: Int?, + val lastUsedTime: Instant?, + val footerDescription: String?, ) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent) class RemoteInfo( @@ -93,7 +95,7 @@ class RemoteInfo( data class RequestDisplayInfo( val title: String, val subtitle: String?, - val type: String, + val type: CredentialType, val appName: String, val typeIcon: Drawable, ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index b30d1ec24d784..a5e19b69e4704 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -16,7 +16,6 @@ package com.android.credentialmanager.getflow -import android.credentials.Credential import android.text.TextUtils import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult @@ -59,6 +58,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.R +import com.android.credentialmanager.common.CredentialType import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.material.ModalBottomSheetLayout import com.android.credentialmanager.common.material.ModalBottomSheetValue @@ -71,7 +71,6 @@ import com.android.credentialmanager.common.ui.TextSecondary import com.android.credentialmanager.common.ui.TextOnSurfaceVariant import com.android.credentialmanager.common.ui.ContainerCard import com.android.credentialmanager.common.ui.TransparentBackgroundEntry -import com.android.credentialmanager.jetpack.developer.PublicKeyCredential import com.android.credentialmanager.ui.theme.EntryShape import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme @@ -171,7 +170,7 @@ fun PrimarySelectionCard( ) { if (sortedUserNameToCredentialEntryList.first() .sortedCredentialEntryList.first().credentialType - == PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL + == CredentialType.PASSKEY ) R.string.get_dialog_title_use_passkey_for else R.string.get_dialog_title_use_sign_in_for } else if ( @@ -534,7 +533,7 @@ fun CredentialEntryRow( ) TextSecondary( text = if ( - credentialEntryInfo.credentialType == Credential.TYPE_PASSWORD_CREDENTIAL) { + credentialEntryInfo.credentialType == CredentialType.PASSWORD) { "••••••••••••" } else { if (TextUtils.isEmpty(credentialEntryInfo.displayName)) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt index 7d2f0dab66a54..5741f368f4f6c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt @@ -26,11 +26,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import com.android.credentialmanager.CredentialManagerRepo +import com.android.credentialmanager.common.CredentialType import com.android.credentialmanager.common.Constants import com.android.credentialmanager.common.DialogState import com.android.credentialmanager.common.ProviderActivityResult import com.android.credentialmanager.common.ProviderActivityState -import com.android.credentialmanager.jetpack.developer.PublicKeyCredential import com.android.internal.util.Preconditions data class GetCredentialUiState( @@ -258,9 +258,9 @@ internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator p1.lastUsedTimeMillis) { return -1 } - } else if (p0.lastUsedTimeMillis != null && p0.lastUsedTimeMillis > 0) { + } else if (p0.lastUsedTimeMillis != null) { return -1 - } else if (p1.lastUsedTimeMillis != null && p1.lastUsedTimeMillis > 0) { + } else if (p1.lastUsedTimeMillis != null) { return 1 } return 0 diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index 8ce31bdda96c9..4c05dea5c13e3 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -19,6 +19,9 @@ package com.android.credentialmanager.getflow import android.app.PendingIntent import android.content.Intent import android.graphics.drawable.Drawable +import com.android.credentialmanager.common.CredentialType + +import java.time.Instant data class ProviderInfo( /** @@ -62,13 +65,13 @@ class CredentialEntryInfo( pendingIntent: PendingIntent?, fillInIntent: Intent?, /** Type of this credential used for sorting. Not localized so must not be directly displayed. */ - val credentialType: String, + val credentialType: CredentialType, /** Localized type value of this credential used for display purpose. */ val credentialTypeDisplayName: String, val userName: String, val displayName: String?, val icon: Drawable?, - val lastUsedTimeMillis: Long?, + val lastUsedTimeMillis: Instant?, ) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent) class AuthenticationEntryInfo( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt deleted file mode 100644 index eaa2ad4b25b42..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt +++ /dev/null @@ -1,74 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.credentials.Credential -import android.os.Bundle -import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE - -/** - * Base request class for registering a credential. - * - * @property type the credential type - * @property data the request data in the [Bundle] format - * @property requireSystemProvider true if must only be fulfilled by a system provider and false - * otherwise - */ -open class CreateCredentialRequest( - open val type: String, - open val credentialData: Bundle, - open val candidateQueryData: Bundle, - open val requireSystemProvider: Boolean -) { - companion object { - @JvmStatic - fun createFrom( - type: String, - credentialData: Bundle, - candidateQueryData: Bundle, - requireSystemProvider: Boolean - ): CreateCredentialRequest { - return try { - when (type) { - Credential.TYPE_PASSWORD_CREDENTIAL -> - CreatePasswordRequest.createFrom(credentialData) - PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> - when (credentialData.getString(BUNDLE_KEY_SUBTYPE)) { - CreatePublicKeyCredentialRequest - .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST -> - CreatePublicKeyCredentialRequest.createFrom(credentialData) - CreatePublicKeyCredentialRequestPrivileged - .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV -> - CreatePublicKeyCredentialRequestPrivileged - .createFrom(credentialData) - else -> throw FrameworkClassParsingException() - } - else -> throw FrameworkClassParsingException() - } - } catch (e: FrameworkClassParsingException) { - // Parsing failed but don't crash the process. Instead just output a request with - // the raw framework values. - CreateCustomCredentialRequest( - type, - credentialData, - candidateQueryData, - requireSystemProvider - ) - } - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt deleted file mode 100644 index 50da9a1b4000e..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.jetpack.developer - -import android.os.Bundle - -/** - * Base custom create request class for registering a credential. - * - * An application can construct a subtype custom request and call - * [CredentialManager.executeCreateCredential] to launch framework UI flows to collect consent and - * any other metadata needed from the user to register a new user credential. - * - * @property type the credential type determined by the credential-type-specific subclass for custom - * use cases - * @property credentialData the full credential creation request data in the [Bundle] format for - * custom use cases - * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to - * the provider during the initial candidate query stage, which should not contain sensitive user - * credential information - * @property requireSystemProvider true if must only be fulfilled by a system provider and false - * otherwise - * @throws IllegalArgumentException If [type] is empty - * @throws NullPointerException If [type] or [credentialData] are null - */ -open class CreateCustomCredentialRequest( - final override val type: String, - final override val credentialData: Bundle, - final override val candidateQueryData: Bundle, - @get:JvmName("requireSystemProvider") - final override val requireSystemProvider: Boolean -) : CreateCredentialRequest(type, credentialData, candidateQueryData, requireSystemProvider) { - init { - require(type.isNotEmpty()) { "type should not be empty" } - } -} \ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt deleted file mode 100644 index bf0aa8aec0787..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt +++ /dev/null @@ -1,76 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.credentials.Credential -import android.os.Bundle - -/** - * A request to save the user password credential with their password provider. - * - * @property id the user id associated with the password - * @property password the password - * @throws NullPointerException If [id] is null - * @throws NullPointerException If [password] is null - * @throws IllegalArgumentException If [password] is empty - */ -class CreatePasswordRequest constructor( - val id: String, - val password: String, -) : CreateCredentialRequest( - type = Credential.TYPE_PASSWORD_CREDENTIAL, - credentialData = toCredentialDataBundle(id, password), - // No credential data should be sent during the query phase. - candidateQueryData = Bundle(), - requireSystemProvider = false, -) { - - init { - require(password.isNotEmpty()) { "password should not be empty" } - } - - companion object { - const val BUNDLE_KEY_ID = "androidx.credentials.BUNDLE_KEY_ID" - const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD" - - @JvmStatic - internal fun toCredentialDataBundle(id: String, password: String): Bundle { - val bundle = Bundle() - bundle.putString(BUNDLE_KEY_ID, id) - bundle.putString(BUNDLE_KEY_PASSWORD, password) - return bundle - } - - @JvmStatic - internal fun toCandidateDataBundle(id: String): Bundle { - val bundle = Bundle() - bundle.putString(BUNDLE_KEY_ID, id) - return bundle - } - - @JvmStatic - internal fun createFrom(data: Bundle): CreatePasswordRequest { - try { - val id = data.getString(BUNDLE_KEY_ID) - val password = data.getString(BUNDLE_KEY_PASSWORD) - return CreatePasswordRequest(id!!, password!!) - } catch (e: Exception) { - throw FrameworkClassParsingException() - } - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt deleted file mode 100644 index f3d402a7534a3..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt +++ /dev/null @@ -1,100 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.os.Bundle -import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE - -/** -* A request to register a passkey from the user's public key credential provider. -* -* @property requestJson the privileged request in JSON format in the standard webauthn web json -* shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson). -* @property preferImmediatelyAvailableCredentials true if you prefer the operation to return -* immediately when there is no available passkey registration offering instead of falling back to -* discovering remote options, and false (default) otherwise -* @throws NullPointerException If [requestJson] is null -* @throws IllegalArgumentException If [requestJson] is empty -*/ -class CreatePublicKeyCredentialRequest @JvmOverloads constructor( - val requestJson: String, - @get:JvmName("preferImmediatelyAvailableCredentials") - val preferImmediatelyAvailableCredentials: Boolean = false -) : CreateCredentialRequest( - type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, - credentialData = toCredentialDataBundle(requestJson, preferImmediatelyAvailableCredentials), - // The whole request data should be passed during the query phase. - candidateQueryData = toCredentialDataBundle(requestJson, preferImmediatelyAvailableCredentials), - requireSystemProvider = false, -) { - - init { - require(requestJson.isNotEmpty()) { "requestJson must not be empty" } - } - - /** @hide */ - companion object { - const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS = - "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS" - internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" - internal const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST = - "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST" - - @JvmStatic - internal fun toCredentialDataBundle( - requestJson: String, - preferImmediatelyAvailableCredentials: Boolean - ): Bundle { - val bundle = Bundle() - bundle.putString(BUNDLE_KEY_SUBTYPE, - BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST) - bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) - bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, - preferImmediatelyAvailableCredentials) - return bundle - } - - @JvmStatic - internal fun toCandidateDataBundle( - requestJson: String, - preferImmediatelyAvailableCredentials: Boolean - ): Bundle { - val bundle = Bundle() - bundle.putString(BUNDLE_KEY_SUBTYPE, - BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST) - bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) - bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, - preferImmediatelyAvailableCredentials) - return bundle - } - - @Suppress("deprecation") // bundle.get() used for boolean value to prevent default - // boolean value from being returned. - @JvmStatic - internal fun createFrom(data: Bundle): CreatePublicKeyCredentialRequest { - try { - val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) - val preferImmediatelyAvailableCredentials = - data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS) - return CreatePublicKeyCredentialRequest(requestJson!!, - (preferImmediatelyAvailableCredentials!!) as Boolean) - } catch (e: Exception) { - throw FrameworkClassParsingException() - } - } - } -} \ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt deleted file mode 100644 index 85ab2d2fec218..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt +++ /dev/null @@ -1,183 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.os.Bundle -import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE - -/** - * A privileged request to register a passkey from the user’s public key credential provider, where - * the caller can modify the rp. Only callers with privileged permission, e.g. user’s default - * brower, caBLE, can use this. These permissions will be introduced in an upcoming release. - * TODO("Add specific permission info/annotation") - * - * @property requestJson the privileged request in JSON format in the standard webauthn web json - * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson). - * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return - * immediately when there is no available passkey registration offering instead of falling back to - * discovering remote options, and false (default) otherwise - * @property relyingParty the expected true RP ID which will override the one in the [requestJson], - * where rp is defined [here](https://w3c.github.io/webauthn/#rp-id) - * @property clientDataHash a hash that is used to verify the [relyingParty] Identity - * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash] is - * null - * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is - * empty - */ -class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor( - val requestJson: String, - val relyingParty: String, - val clientDataHash: String, - @get:JvmName("preferImmediatelyAvailableCredentials") - val preferImmediatelyAvailableCredentials: Boolean = false -) : CreateCredentialRequest( - type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, - credentialData = toCredentialDataBundle( - requestJson, - relyingParty, - clientDataHash, - preferImmediatelyAvailableCredentials - ), - // The whole request data should be passed during the query phase. - candidateQueryData = toCredentialDataBundle( - requestJson, relyingParty, clientDataHash, preferImmediatelyAvailableCredentials - ), - requireSystemProvider = false, -) { - - init { - require(requestJson.isNotEmpty()) { "requestJson must not be empty" } - require(relyingParty.isNotEmpty()) { "rp must not be empty" } - require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" } - } - - /** A builder for [CreatePublicKeyCredentialRequestPrivileged]. */ - class Builder( - private var requestJson: String, - private var relyingParty: String, - private var clientDataHash: String - ) { - - private var preferImmediatelyAvailableCredentials: Boolean = false - - /** - * Sets the privileged request in JSON format. - */ - fun setRequestJson(requestJson: String): Builder { - this.requestJson = requestJson - return this - } - - /** - * Sets to true if you prefer the operation to return immediately when there is no available - * passkey registration offering instead of falling back to discovering remote options, and - * false otherwise. - * - * The default value is false. - */ - @Suppress("MissingGetterMatchingBuilder") - fun setPreferImmediatelyAvailableCredentials( - preferImmediatelyAvailableCredentials: Boolean - ): Builder { - this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials - return this - } - - /** - * Sets the expected true RP ID which will override the one in the [requestJson]. - */ - fun setRelyingParty(relyingParty: String): Builder { - this.relyingParty = relyingParty - return this - } - - /** - * Sets a hash that is used to verify the [relyingParty] Identity. - */ - fun setClientDataHash(clientDataHash: String): Builder { - this.clientDataHash = clientDataHash - return this - } - - /** Builds a [CreatePublicKeyCredentialRequestPrivileged]. */ - fun build(): CreatePublicKeyCredentialRequestPrivileged { - return CreatePublicKeyCredentialRequestPrivileged( - this.requestJson, - this.relyingParty, this.clientDataHash, this.preferImmediatelyAvailableCredentials - ) - } - } - - /** @hide */ - companion object { - internal const val BUNDLE_KEY_RELYING_PARTY = - "androidx.credentials.BUNDLE_KEY_RELYING_PARTY" - internal const val BUNDLE_KEY_CLIENT_DATA_HASH = - "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH" - internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS = - "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS" - - internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" - - internal const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV = - "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_" + - "PRIVILEGED" - - @JvmStatic - internal fun toCredentialDataBundle( - requestJson: String, - relyingParty: String, - clientDataHash: String, - preferImmediatelyAvailableCredentials: Boolean - ): Bundle { - val bundle = Bundle() - bundle.putString( - PublicKeyCredential.BUNDLE_KEY_SUBTYPE, - BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV - ) - bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) - bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty) - bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash) - bundle.putBoolean( - BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, - preferImmediatelyAvailableCredentials - ) - return bundle - } - - @Suppress("deprecation") // bundle.get() used for boolean value to prevent default - // boolean value from being returned. - @JvmStatic - internal fun createFrom(data: Bundle): CreatePublicKeyCredentialRequestPrivileged { - try { - val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) - val rp = data.getString(BUNDLE_KEY_RELYING_PARTY) - val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH) - val preferImmediatelyAvailableCredentials = - data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS) - return CreatePublicKeyCredentialRequestPrivileged( - requestJson!!, - rp!!, - clientDataHash!!, - (preferImmediatelyAvailableCredentials!!) as Boolean, - ) - } catch (e: Exception) { - throw FrameworkClassParsingException() - } - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt deleted file mode 100644 index ee08e9e306491..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt +++ /dev/null @@ -1,27 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.os.Bundle - -/** - * Base class for a credential with which the user consented to authenticate to the app. - * - * @property type the credential type - * @property data the credential data in the [Bundle] format. - */ -open class Credential(val type: String, val data: Bundle) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt deleted file mode 100644 index fc7b7de6f3bba..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt +++ /dev/null @@ -1,68 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.credentials.Credential -import android.os.Bundle - -/** - * Base request class for getting a registered credential. - * - * @property type the credential type - * @property data the request data in the [Bundle] format - * @property requireSystemProvider true if must only be fulfilled by a system provider and false - * otherwise - */ -open class GetCredentialOption( - open val type: String, - open val requestData: Bundle, - open val candidateQueryData: Bundle, - open val requireSystemProvider: Boolean, -) { - companion object { - @JvmStatic - fun createFrom( - type: String, - requestData: Bundle, - candidateQueryData: Bundle, - requireSystemProvider: Boolean - ): GetCredentialOption { - return try { - when (type) { - Credential.TYPE_PASSWORD_CREDENTIAL -> - GetPasswordOption.createFrom(requestData) - PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> - when (requestData.getString(PublicKeyCredential.BUNDLE_KEY_SUBTYPE)) { - GetPublicKeyCredentialOption - .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION -> - GetPublicKeyCredentialOption.createFrom(requestData) - GetPublicKeyCredentialOptionPrivileged - .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED -> - GetPublicKeyCredentialOptionPrivileged.createFrom(requestData) - else -> throw FrameworkClassParsingException() - } - else -> throw FrameworkClassParsingException() - } - } catch (e: FrameworkClassParsingException) { - // Parsing failed but don't crash the process. Instead just output a request with - // the raw framework values. - GetCustomCredentialOption( - type, requestData, candidateQueryData, requireSystemProvider) - } - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt deleted file mode 100644 index fdd57ffe82a02..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt +++ /dev/null @@ -1,75 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -/** - * Encapsulates a request to get a user credential. - * - * @property getCredentialOptions the list of [GetCredentialOption] from which the user can choose - * one to authenticate to the app - * @throws IllegalArgumentException If [getCredentialOptions] is empty - */ -class GetCredentialRequest constructor( - val getCredentialOptions: List, -) { - - init { - require(getCredentialOptions.isNotEmpty()) { "credentialRequests should not be empty" } - } - - /** A builder for [GetCredentialRequest]. */ - class Builder { - private var getCredentialOptions: MutableList = mutableListOf() - - /** Adds a specific type of [GetCredentialOption]. */ - fun addGetCredentialOption(getCredentialOption: GetCredentialOption): Builder { - getCredentialOptions.add(getCredentialOption) - return this - } - - /** Sets the list of [GetCredentialOption]. */ - fun setGetCredentialOptions(getCredentialOptions: List): Builder { - this.getCredentialOptions = getCredentialOptions.toMutableList() - return this - } - - /** - * Builds a [GetCredentialRequest]. - * - * @throws IllegalArgumentException If [getCredentialOptions] is empty - */ - fun build(): GetCredentialRequest { - return GetCredentialRequest(getCredentialOptions.toList()) - } - } - - companion object { - @JvmStatic - fun createFrom(from: android.credentials.GetCredentialRequest): GetCredentialRequest { - return GetCredentialRequest( - from.credentialOptions.map { - GetCredentialOption.createFrom( - it.type, - it.credentialRetrievalData, - it.candidateQueryData, - it.isSystemProviderRequired() - ) - } - ) - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt deleted file mode 100644 index 803885cbcdd0b..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.jetpack.developer - -import android.os.Bundle - -/** - * Allows extending custom versions of GetCredentialOptions for unique use cases. - * - * @property type the credential type determined by the credential-type-specific subclass - * generated for custom use cases - * @property requestData the request data in the [Bundle] format, generated for custom use cases - * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to - * the provider during the initial candidate query stage, which should not contain sensitive user - * information - * @property requireSystemProvider true if must only be fulfilled by a system provider and false - * otherwise - * @throws IllegalArgumentException If [type] is empty - * @throws NullPointerException If [requestData] or [type] is null - */ -open class GetCustomCredentialOption( - final override val type: String, - final override val requestData: Bundle, - final override val candidateQueryData: Bundle, - @get:JvmName("requireSystemProvider") - final override val requireSystemProvider: Boolean -) : GetCredentialOption( - type, - requestData, - candidateQueryData, - requireSystemProvider -) { - init { - require(type.isNotEmpty()) { "type should not be empty" } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt deleted file mode 100644 index 2b9cfa30928cd..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt +++ /dev/null @@ -1,35 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.credentials.Credential -import android.os.Bundle - -/** A request to retrieve the user's saved application password from their password provider. */ -class GetPasswordOption : GetCredentialOption( - Credential.TYPE_PASSWORD_CREDENTIAL, - Bundle(), - Bundle(), - false, -) { - companion object { - @JvmStatic - fun createFrom(data: Bundle): GetPasswordOption { - return GetPasswordOption() - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt deleted file mode 100644 index 2f9b24936e24e..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt +++ /dev/null @@ -1,85 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.os.Bundle - -/** - * A request to get passkeys from the user's public key credential provider. - * - * @property requestJson the privileged request in JSON format in the standard webauthn web json - * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson). - * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return - * immediately when there is no available credential instead of falling back to discovering remote - * credentials, and false (default) otherwise - * @throws NullPointerException If [requestJson] is null - * @throws IllegalArgumentException If [requestJson] is empty - */ -class GetPublicKeyCredentialOption @JvmOverloads constructor( - val requestJson: String, - @get:JvmName("preferImmediatelyAvailableCredentials") - val preferImmediatelyAvailableCredentials: Boolean = false, -) : GetCredentialOption( - type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, - requestData = toRequestDataBundle(requestJson, preferImmediatelyAvailableCredentials), - candidateQueryData = toRequestDataBundle(requestJson, preferImmediatelyAvailableCredentials), - requireSystemProvider = false -) { - init { - require(requestJson.isNotEmpty()) { "requestJson must not be empty" } - } - - /** @hide */ - companion object { - internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS = - "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS" - internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" - internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION = - "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" - - @JvmStatic - internal fun toRequestDataBundle( - requestJson: String, - preferImmediatelyAvailableCredentials: Boolean - ): Bundle { - val bundle = Bundle() - bundle.putString( - PublicKeyCredential.BUNDLE_KEY_SUBTYPE, - BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION - ) - bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) - bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, - preferImmediatelyAvailableCredentials) - return bundle - } - - @Suppress("deprecation") // bundle.get() used for boolean value to prevent default - // boolean value from being returned. - @JvmStatic - internal fun createFrom(data: Bundle): GetPublicKeyCredentialOption { - try { - val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) - val preferImmediatelyAvailableCredentials = - data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS) - return GetPublicKeyCredentialOption(requestJson!!, - (preferImmediatelyAvailableCredentials!!) as Boolean) - } catch (e: Exception) { - throw FrameworkClassParsingException() - } - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt deleted file mode 100644 index 6f4782a351c1f..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt +++ /dev/null @@ -1,182 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.os.Bundle - -/** - * A privileged request to get passkeys from the user's public key credential provider. The caller - * can modify the RP. Only callers with privileged permission (e.g. user's public browser or caBLE) - * can use this. These permissions will be introduced in an upcoming release. - * TODO("Add specific permission info/annotation") - * - * @property requestJson the privileged request in JSON format in the standard webauthn web json - * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson). - * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return - * immediately when there is no available credential instead of falling back to discovering remote - * credentials, and false (default) otherwise - * @property relyingParty the expected true RP ID which will override the one in the [requestJson], - * where relyingParty is defined [here](https://w3c.github.io/webauthn/#rp-id) in more detail - * @property clientDataHash a hash that is used to verify the [relyingParty] Identity - * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash] - * is null - * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is - * empty - */ -class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor( - val requestJson: String, - val relyingParty: String, - val clientDataHash: String, - @get:JvmName("preferImmediatelyAvailableCredentials") - val preferImmediatelyAvailableCredentials: Boolean = false -) : GetCredentialOption( - type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, - requestData = toBundle( - requestJson, - relyingParty, - clientDataHash, - preferImmediatelyAvailableCredentials - ), - candidateQueryData = toBundle( - requestJson, - relyingParty, - clientDataHash, - preferImmediatelyAvailableCredentials - ), - requireSystemProvider = false, -) { - - init { - require(requestJson.isNotEmpty()) { "requestJson must not be empty" } - require(relyingParty.isNotEmpty()) { "rp must not be empty" } - require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" } - } - - /** A builder for [GetPublicKeyCredentialOptionPrivileged]. */ - class Builder( - private var requestJson: String, - private var relyingParty: String, - private var clientDataHash: String - ) { - - private var preferImmediatelyAvailableCredentials: Boolean = false - - /** - * Sets the privileged request in JSON format. - */ - fun setRequestJson(requestJson: String): Builder { - this.requestJson = requestJson - return this - } - - /** - * Sets to true if you prefer the operation to return immediately when there is no available - * credential instead of falling back to discovering remote credentials, and false - * otherwise. - * - * The default value is false. - */ - @Suppress("MissingGetterMatchingBuilder") - fun setPreferImmediatelyAvailableCredentials( - preferImmediatelyAvailableCredentials: Boolean - ): Builder { - this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials - return this - } - - /** - * Sets the expected true RP ID which will override the one in the [requestJson]. - */ - fun setRelyingParty(relyingParty: String): Builder { - this.relyingParty = relyingParty - return this - } - - /** - * Sets a hash that is used to verify the [relyingParty] Identity. - */ - fun setClientDataHash(clientDataHash: String): Builder { - this.clientDataHash = clientDataHash - return this - } - - /** Builds a [GetPublicKeyCredentialOptionPrivileged]. */ - fun build(): GetPublicKeyCredentialOptionPrivileged { - return GetPublicKeyCredentialOptionPrivileged( - this.requestJson, - this.relyingParty, this.clientDataHash, this.preferImmediatelyAvailableCredentials - ) - } - } - - /** @hide */ - companion object { - internal const val BUNDLE_KEY_RELYING_PARTY = - "androidx.credentials.BUNDLE_KEY_RELYING_PARTY" - internal const val BUNDLE_KEY_CLIENT_DATA_HASH = - "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH" - internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS = - "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS" - internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" - internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED = - "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" + - "_PRIVILEGED" - - @JvmStatic - internal fun toBundle( - requestJson: String, - relyingParty: String, - clientDataHash: String, - preferImmediatelyAvailableCredentials: Boolean - ): Bundle { - val bundle = Bundle() - bundle.putString( - PublicKeyCredential.BUNDLE_KEY_SUBTYPE, - BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED - ) - bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) - bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty) - bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash) - bundle.putBoolean( - BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, - preferImmediatelyAvailableCredentials - ) - return bundle - } - - @Suppress("deprecation") // bundle.get() used for boolean value to prevent default - // boolean value from being returned. - @JvmStatic - internal fun createFrom(data: Bundle): GetPublicKeyCredentialOptionPrivileged { - try { - val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) - val rp = data.getString(BUNDLE_KEY_RELYING_PARTY) - val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH) - val preferImmediatelyAvailableCredentials = - data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS) - return GetPublicKeyCredentialOptionPrivileged( - requestJson!!, - rp!!, - clientDataHash!!, - (preferImmediatelyAvailableCredentials!!) as Boolean, - ) - } catch (e: Exception) { - throw FrameworkClassParsingException() - } - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PasswordCredential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PasswordCredential.kt deleted file mode 100644 index 165885843a352..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PasswordCredential.kt +++ /dev/null @@ -1,55 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.os.Bundle - -class PasswordCredential constructor( - val id: String, - val password: String, -) : Credential(android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL, toBundle(id, password)) { - - init { - require(password.isNotEmpty()) { "password should not be empty" } - } - - /** @hide */ - companion object { - - const val BUNDLE_KEY_ID = "androidx.credentials.BUNDLE_KEY_ID" - const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD" - - @JvmStatic - internal fun toBundle(id: String, password: String): Bundle { - val bundle = Bundle() - bundle.putString(BUNDLE_KEY_ID, id) - bundle.putString(BUNDLE_KEY_PASSWORD, password) - return bundle - } - - @JvmStatic - internal fun createFrom(data: Bundle): PasswordCredential { - try { - val id = data.getString(BUNDLE_KEY_ID) - val password = data.getString(BUNDLE_KEY_PASSWORD) - return PasswordCredential(id!!, password!!) - } catch (e: Exception) { - throw FrameworkClassParsingException() - } - } - } -} \ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt deleted file mode 100644 index 6a811671d3e63..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt +++ /dev/null @@ -1,60 +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. - */ - -package com.android.credentialmanager.jetpack.developer - -import android.os.Bundle - -/** - * Represents the user's passkey credential granted by the user for app sign-in. - * - * @property authenticationResponseJson the public key credential authentication response in - * JSON format that follows the standard webauthn json format shown at - * [this w3c link](https://w3c.github.io/webauthn/#dictdef-authenticationresponsejson) - * @throws NullPointerException If [authenticationResponseJson] is null. This is handled by the - * kotlin runtime - * @throws IllegalArgumentException If [authenticationResponseJson] is empty - * - * @hide - */ -class PublicKeyCredential constructor( - val authenticationResponseJson: String -) : Credential( - TYPE_PUBLIC_KEY_CREDENTIAL, - toBundle(authenticationResponseJson) -) { - - init { - require(authenticationResponseJson.isNotEmpty()) { - "authentication response JSON must not be empty" } - } - companion object { - /** The type value for public key credential related operations. */ - const val TYPE_PUBLIC_KEY_CREDENTIAL: String = - "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" - /** The Bundle key value for the public key credential subtype (privileged or regular). */ - internal const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE" - const val BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON = - "androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON" - - @JvmStatic - internal fun toBundle(authenticationResponseJson: String): Bundle { - val bundle = Bundle() - bundle.putString(BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON, authenticationResponseJson) - return bundle - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/Action.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/Action.kt deleted file mode 100644 index 1abf9113845fc..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/Action.kt +++ /dev/null @@ -1,100 +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. - */ - -package com.android.credentialmanager.jetpack.provider - -import android.annotation.SuppressLint -import android.app.PendingIntent -import android.app.slice.Slice -import android.app.slice.SliceSpec -import android.net.Uri -import android.util.Log -import java.util.Collections - -/** - * UI representation for a credential entry used during the get credential flow. - * - * TODO: move to jetpack. - */ -class Action constructor( - val title: CharSequence, - val subTitle: CharSequence?, - val pendingIntent: PendingIntent?, -) { - - init { - require(title.isNotEmpty()) { "title must not be empty" } - } - - companion object { - private const val TAG = "Action" - internal const val SLICE_HINT_TITLE = - "androidx.credentials.provider.action.HINT_ACTION_TITLE" - internal const val SLICE_HINT_SUBTITLE = - "androidx.credentials.provider.action.HINT_ACTION_SUBTEXT" - internal const val SLICE_HINT_PENDING_INTENT = - "androidx.credentials.provider.action.SLICE_HINT_PENDING_INTENT" - - @JvmStatic - fun toSlice(action: Action): Slice { - // TODO("Put the right spec and version value") - val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("type", 1)) - .addText(action.title, /*subType=*/null, - listOf(SLICE_HINT_TITLE)) - .addText(action.subTitle, /*subType=*/null, - listOf(SLICE_HINT_SUBTITLE)) - if (action.pendingIntent != null) { - sliceBuilder.addAction(action.pendingIntent, - Slice.Builder(sliceBuilder) - .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT)) - .build(), - /*subType=*/null) - } - return sliceBuilder.build() - } - - /** - * Returns an instance of [Action] derived from a [Slice] object. - * - * @param slice the [Slice] object constructed through [toSlice] - */ - @SuppressLint("WrongConstant") // custom conversion between jetpack and framework - @JvmStatic - fun fromSlice(slice: Slice): Action? { - // TODO("Put the right spec and version value") - var title: CharSequence = "" - var subTitle: CharSequence? = null - var pendingIntent: PendingIntent? = null - - slice.items.forEach { - if (it.hasHint(SLICE_HINT_TITLE)) { - title = it.text - } else if (it.hasHint(SLICE_HINT_SUBTITLE)) { - subTitle = it.text - } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) { - pendingIntent = it.action - } - } - - return try { - Action(title, subTitle, pendingIntent) - } catch (e: Exception) { - Log.i(TAG, "fromSlice failed with: " + e.message) - null - } - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/AuthenticationAction.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/AuthenticationAction.kt deleted file mode 100644 index 283c7ba16fa5e..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/AuthenticationAction.kt +++ /dev/null @@ -1,55 +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. - */ - -package com.android.credentialmanager.jetpack.provider - -import android.app.PendingIntent -import android.app.slice.Slice -import android.util.Log -import androidx.annotation.VisibleForTesting - -/** - * UI representation for a credential entry used during the get credential flow. - * - * TODO: move to jetpack. - */ -class AuthenticationAction constructor( - val pendingIntent: PendingIntent -) { - - - companion object { - private const val TAG = "AuthenticationAction" - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal const val SLICE_HINT_PENDING_INTENT = - "androidx.credentials.provider.authenticationAction.SLICE_HINT_PENDING_INTENT" - - @JvmStatic - fun fromSlice(slice: Slice): AuthenticationAction? { - slice.items.forEach { - if (it.hasHint(SLICE_HINT_PENDING_INTENT)) { - return try { - AuthenticationAction(it.action) - } catch (e: Exception) { - Log.i(TAG, "fromSlice failed with: " + e.message) - null - } - } - } - return null - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CreateEntry.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CreateEntry.kt deleted file mode 100644 index 0ec91d67241f1..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CreateEntry.kt +++ /dev/null @@ -1,243 +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. - */ - -package com.android.credentialmanager.jetpack.provider - -import android.annotation.SuppressLint -import android.app.PendingIntent -import android.app.slice.Slice -import android.app.slice.SliceSpec -import android.graphics.drawable.Icon -import android.net.Uri -import android.os.Bundle -import android.util.Log -import java.util.Collections - -/** - * UI representation for a save entry used during the create credential flow. - * - * TODO: move to jetpack. - */ -class CreateEntry internal constructor( - val accountName: CharSequence, - val pendingIntent: PendingIntent?, - val icon: Icon?, - val lastUsedTimeMillis: Long, - val credentialCountInformationList: List, - val footerDescription: CharSequence?, -) { - - init { - require(accountName.isNotEmpty()) { "accountName must not be empty" } - } - - /** - * A builder for [CreateEntry] - * - * @property accountName the name of the account where the credential will be registered - * @property pendingIntent the [PendingIntent] that will be fired when the user selects - * this entry - * - * @hide - */ - class Builder constructor( - private val accountName: CharSequence, - private val pendingIntent: PendingIntent? = null - ) { - - private var credentialCountInformationList: MutableList = - mutableListOf() - private var icon: Icon? = null - private var lastUsedTimeMillis: Long = 0 - private var footerDescription: CharSequence? = null - - /** Adds a [CredentialCountInformation] denoting a given credential - * type and the count of credentials that the provider has stored for that - * credential type. - * - * This information will be displayed on the [CreateEntry] to help the user - * make a choice. - */ - @Suppress("MissingGetterMatchingBuilder") - fun addCredentialCountInformation(info: CredentialCountInformation): Builder { - credentialCountInformationList.add(info) - return this - } - - /** Sets a list of [CredentialCountInformation]. Each item in the list denotes a given - * credential type and the count of credentials that the provider has stored of that - * credential type. - * - * This information will be displayed on the [CreateEntry] to help the user - * make a choice. - */ - fun setCredentialCountInformationList(infoList: List): Builder { - credentialCountInformationList = infoList as MutableList - return this - } - - /** Sets an icon to be displayed with the entry on the UI */ - fun setIcon(icon: Icon?): Builder { - this.icon = icon - return this - } - - /** Sets the last time this account was used */ - fun setLastUsedTimeMillis(lastUsedTimeMillis: Long): Builder { - this.lastUsedTimeMillis = lastUsedTimeMillis - return this - } - - /** Sets the footer description of this */ - fun setFooterDescription(footerDescription: CharSequence): Builder { - this.footerDescription = footerDescription - return this - } - - /** - * Builds an instance of [CreateEntry] - * - * @throws IllegalArgumentException If [accountName] is empty - */ - fun build(): CreateEntry { - return CreateEntry(accountName, pendingIntent, icon, lastUsedTimeMillis, - credentialCountInformationList, footerDescription) - } - } - - companion object { - private const val TAG = "CreateEntry" - internal const val SLICE_HINT_ACCOUNT_NAME = - "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME" - internal const val SLICE_HINT_ICON = - "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON" - internal const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION = - "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION" - internal const val SLICE_HINT_LAST_USED_TIME_MILLIS = - "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS" - internal const val SLICE_HINT_PENDING_INTENT = - "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT" - internal const val SLICE_HINT_FOOTER_DESCRIPTION = - "androidx.credentials.provider.createEntry.SLICE_HINT_FOOTER_DESCRIPTION" - - @JvmStatic - fun toSlice(createEntry: CreateEntry): Slice { - // TODO("Use the right type and revision") - val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("type", 1)) - sliceBuilder.addText(createEntry.accountName, /*subType=*/null, - listOf(SLICE_HINT_ACCOUNT_NAME)) - .addLong(createEntry.lastUsedTimeMillis, /*subType=*/null, listOf( - SLICE_HINT_LAST_USED_TIME_MILLIS)) - if (createEntry.icon != null) { - sliceBuilder.addIcon(createEntry.icon, /*subType=*/null, - listOf(SLICE_HINT_ICON)) - } - - val credentialCountBundle = convertCredentialCountInfoToBundle( - createEntry.credentialCountInformationList) - if (credentialCountBundle != null) { - sliceBuilder.addBundle(convertCredentialCountInfoToBundle( - createEntry.credentialCountInformationList), null, listOf( - SLICE_HINT_CREDENTIAL_COUNT_INFORMATION)) - } - if (createEntry.pendingIntent != null) { - sliceBuilder.addAction(createEntry.pendingIntent, - Slice.Builder(sliceBuilder) - .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT)) - .build(), - /*subType=*/null) - } - if (createEntry.footerDescription != null) { - sliceBuilder.addText(createEntry.footerDescription, /*subType=*/null, - listOf(SLICE_HINT_FOOTER_DESCRIPTION)) - } - return sliceBuilder.build() - } - - /** - * Returns an instance of [CreateEntry] derived from a [Slice] object. - * - * @param slice the [Slice] object constructed through [toSlice] - */ - @SuppressLint("WrongConstant") // custom conversion between jetpack and framework - @JvmStatic - fun fromSlice(slice: Slice): CreateEntry? { - // TODO("Put the right spec and version value") - var accountName: CharSequence = "" - var icon: Icon? = null - var pendingIntent: PendingIntent? = null - var credentialCountInfo: List = listOf() - var lastUsedTimeMillis: Long = 0 - var footerDescription: CharSequence? = null - - slice.items.forEach { - if (it.hasHint(SLICE_HINT_ACCOUNT_NAME)) { - accountName = it.text - } else if (it.hasHint(SLICE_HINT_ICON)) { - icon = it.icon - } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) { - pendingIntent = it.action - } else if (it.hasHint(SLICE_HINT_CREDENTIAL_COUNT_INFORMATION)) { - credentialCountInfo = convertBundleToCredentialCountInfo(it.bundle) - } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) { - lastUsedTimeMillis = it.long - } else if (it.hasHint(SLICE_HINT_FOOTER_DESCRIPTION)) { - footerDescription = it.text - } - } - - return try { - CreateEntry(accountName, pendingIntent, icon, - lastUsedTimeMillis, credentialCountInfo, footerDescription) - } catch (e: Exception) { - Log.i(TAG, "fromSlice failed with: " + e.message) - null - } - } - - @JvmStatic - internal fun convertBundleToCredentialCountInfo(bundle: Bundle?): - List { - val credentialCountList = ArrayList() - if (bundle == null) { - return credentialCountList - } - bundle.keySet().forEach { - try { - credentialCountList.add( - CredentialCountInformation(it, bundle.getInt(it))) - } catch (e: Exception) { - Log.i(TAG, "Issue unpacking credential count info bundle: " + e.message) - } - } - return credentialCountList - } - - @JvmStatic - internal fun convertCredentialCountInfoToBundle( - credentialCountInformationList: List - ): Bundle? { - if (credentialCountInformationList.isEmpty()) { - return null - } - val bundle = Bundle() - credentialCountInformationList.forEach { - bundle.putInt(it.type, it.count) - } - return bundle - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialCountInformation.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialCountInformation.kt deleted file mode 100644 index aa77b74ce76a1..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialCountInformation.kt +++ /dev/null @@ -1,61 +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. - */ - -package com.android.credentialmanager.jetpack.provider - -import android.credentials.Credential -import com.android.credentialmanager.jetpack.developer.PublicKeyCredential - -class CredentialCountInformation constructor( - val type: String, - val count: Int -) { - companion object { - @JvmStatic - fun createPasswordCountInformation(count: Int): CredentialCountInformation { - return CredentialCountInformation(Credential.TYPE_PASSWORD_CREDENTIAL, count) - } - - @JvmStatic - fun getPasswordCount(infos: List): Int? { - return getCountForType(infos, Credential.TYPE_PASSWORD_CREDENTIAL) - } - - @JvmStatic - fun createPublicKeyCountInformation(count: Int): CredentialCountInformation { - return CredentialCountInformation(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, count) - } - - @JvmStatic - fun getPasskeyCount(infos: List): Int? { - return getCountForType(infos, PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL) - } - - @JvmStatic - fun createTotalCountInformation(count: Int): CredentialCountInformation { - return CredentialCountInformation("TOTAL_COUNT", count) - } - - @JvmStatic - fun getTotalCount(infos: List): Int? { - return getCountForType(infos, "TOTAL_COUNT") - } - - private fun getCountForType(infos: List, type: String): Int? { - return infos.firstOrNull { info -> info.type == type }?.count - } - } -} \ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntry.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntry.kt deleted file mode 100644 index 61a104bd8e3a2..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntry.kt +++ /dev/null @@ -1,150 +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. - */ - -package com.android.credentialmanager.jetpack.provider - -import android.annotation.SuppressLint -import android.app.PendingIntent -import android.app.slice.Slice -import android.app.slice.SliceSpec -import android.graphics.drawable.Icon -import android.net.Uri -import android.util.Log -import java.util.Collections - -/** - * UI representation for a credential entry used during the get credential flow. - * - * TODO: move to jetpack. - */ -open class CredentialEntry constructor( - // TODO("Add credential type display name for both CredentialEntry & CreateEntry") - val type: String, - val typeDisplayName: CharSequence, - val username: CharSequence, - val displayName: CharSequence?, - val pendingIntent: PendingIntent?, - // TODO("Consider using Instant or other strongly typed time data type") - val lastUsedTimeMillis: Long, - val icon: Icon?, - var autoSelectAllowed: Boolean -) { - init { - require(type.isNotEmpty()) { "type must not be empty" } - require(username.isNotEmpty()) { "type must not be empty" } - } - - companion object { - private const val TAG = "CredentialEntry" - internal const val SLICE_HINT_TYPE_DISPLAY_NAME = - "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME" - internal const val SLICE_HINT_USERNAME = - "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME" - internal const val SLICE_HINT_DISPLAYNAME = - "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME" - internal const val SLICE_HINT_LAST_USED_TIME_MILLIS = - "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS" - internal const val SLICE_HINT_ICON = - "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON" - internal const val SLICE_HINT_PENDING_INTENT = - "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT" - internal const val SLICE_HINT_AUTO_ALLOWED = - "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED" - internal const val AUTO_SELECT_TRUE_STRING = "true" - internal const val AUTO_SELECT_FALSE_STRING = "false" - - @JvmStatic - internal fun toSlice(credentialEntry: CredentialEntry): Slice { - // TODO("Put the right revision value") - val autoSelectAllowed = if (credentialEntry.autoSelectAllowed) { - AUTO_SELECT_TRUE_STRING - } else { - AUTO_SELECT_FALSE_STRING - } - val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec( - credentialEntry.type, 1)) - .addText(credentialEntry.typeDisplayName, /*subType=*/null, - listOf(SLICE_HINT_TYPE_DISPLAY_NAME)) - .addText(credentialEntry.username, /*subType=*/null, - listOf(SLICE_HINT_USERNAME)) - .addText(credentialEntry.displayName, /*subType=*/null, - listOf(SLICE_HINT_DISPLAYNAME)) - .addLong(credentialEntry.lastUsedTimeMillis, /*subType=*/null, - listOf(SLICE_HINT_LAST_USED_TIME_MILLIS)) - .addText(autoSelectAllowed, /*subType=*/null, - listOf(SLICE_HINT_AUTO_ALLOWED)) - if (credentialEntry.icon != null) { - sliceBuilder.addIcon(credentialEntry.icon, /*subType=*/null, - listOf(SLICE_HINT_ICON)) - } - if (credentialEntry.pendingIntent != null) { - sliceBuilder.addAction(credentialEntry.pendingIntent, - Slice.Builder(sliceBuilder) - .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT)) - .build(), - /*subType=*/null) - } - return sliceBuilder.build() - } - - /** - * Returns an instance of [CredentialEntry] derived from a [Slice] object. - * - * @param slice the [Slice] object constructed through [toSlice] - */ - @SuppressLint("WrongConstant") // custom conversion between jetpack and framework - @JvmStatic - fun fromSlice(slice: Slice): CredentialEntry? { - var typeDisplayName: CharSequence? = null - var username: CharSequence? = null - var displayName: CharSequence? = null - var icon: Icon? = null - var pendingIntent: PendingIntent? = null - var lastUsedTimeMillis: Long = 0 - var autoSelectAllowed = false - - slice.items.forEach { - if (it.hasHint(SLICE_HINT_TYPE_DISPLAY_NAME)) { - typeDisplayName = it.text - } else if (it.hasHint(SLICE_HINT_USERNAME)) { - username = it.text - } else if (it.hasHint(SLICE_HINT_DISPLAYNAME)) { - displayName = it.text - } else if (it.hasHint(SLICE_HINT_ICON)) { - icon = it.icon - } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) { - pendingIntent = it.action - } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) { - lastUsedTimeMillis = it.long - } else if (it.hasHint(SLICE_HINT_AUTO_ALLOWED)) { - val autoSelectValue = it.text - if (autoSelectValue == AUTO_SELECT_TRUE_STRING) { - autoSelectAllowed = true - } - } - } - - return try { - CredentialEntry(slice.spec!!.type, typeDisplayName!!, username!!, - displayName, pendingIntent, - lastUsedTimeMillis, icon, autoSelectAllowed) - } catch (e: Exception) { - Log.i(TAG, "fromSlice failed with: " + e.message) - null - } - } - } -}