Merge "Launch provider pending intent from the CredentialManager UI."
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package com.android.credentialmanager
|
||||
|
||||
import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
|
||||
import android.app.PendingIntent
|
||||
import android.app.slice.Slice
|
||||
import android.app.slice.SliceSpec
|
||||
import android.content.Context
|
||||
@@ -32,6 +33,7 @@ import android.credentials.ui.DisabledProviderData
|
||||
import android.credentials.ui.ProviderData
|
||||
import android.credentials.ui.RequestInfo
|
||||
import android.credentials.ui.BaseDialogResult
|
||||
import android.credentials.ui.ProviderPendingIntentResponse
|
||||
import android.credentials.ui.UserSelectionDialogResult
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Binder
|
||||
@@ -54,7 +56,7 @@ class CredentialManagerRepo(
|
||||
private val context: Context,
|
||||
intent: Intent,
|
||||
) {
|
||||
private val requestInfo: RequestInfo
|
||||
val requestInfo: RequestInfo
|
||||
private val providerEnabledList: List<ProviderData>
|
||||
private val providerDisabledList: List<DisabledProviderData>
|
||||
// TODO: require non-null.
|
||||
@@ -75,7 +77,7 @@ class CredentialManagerRepo(
|
||||
RequestInfo.TYPE_GET ->
|
||||
intent.extras?.getParcelableArrayList(
|
||||
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
|
||||
DisabledProviderData::class.java
|
||||
GetCredentialProviderData::class.java
|
||||
) ?: testGetCredentialProviderList()
|
||||
else -> {
|
||||
// TODO: fail gracefully
|
||||
@@ -101,12 +103,19 @@ class CredentialManagerRepo(
|
||||
resultReceiver?.send(BaseDialogResult.RESULT_CODE_DIALOG_CANCELED, resultData)
|
||||
}
|
||||
|
||||
fun onOptionSelected(providerPackageName: String, entryKey: String, entrySubkey: String) {
|
||||
fun onOptionSelected(
|
||||
providerPackageName: String,
|
||||
entryKey: String,
|
||||
entrySubkey: String,
|
||||
resultCode: Int? = null,
|
||||
resultData: Intent? = null,
|
||||
) {
|
||||
val userSelectionDialogResult = UserSelectionDialogResult(
|
||||
requestInfo.token,
|
||||
providerPackageName,
|
||||
entryKey,
|
||||
entrySubkey
|
||||
entrySubkey,
|
||||
if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
|
||||
)
|
||||
val resultData = Bundle()
|
||||
UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultData)
|
||||
@@ -328,6 +337,14 @@ class CredentialManagerRepo(
|
||||
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 slice = Slice.Builder(
|
||||
Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
|
||||
).addText(
|
||||
@@ -347,7 +364,9 @@ class CredentialManagerRepo(
|
||||
return Entry(
|
||||
key,
|
||||
subkey,
|
||||
slice.build()
|
||||
slice.build(),
|
||||
pendingIntent,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
@@ -360,10 +379,22 @@ class CredentialManagerRepo(
|
||||
totalCredentialCount: Int,
|
||||
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 createPasswordRequest = android.service.credentials.CreateCredentialRequest(
|
||||
context.applicationInfo.packageName,
|
||||
"PASSWORD",
|
||||
toBundle("beckett-bakert@gmail.com", "password123")
|
||||
)
|
||||
val fillInIntent = Intent().putExtra("create_request_params", createPasswordRequest)
|
||||
|
||||
val slice = Slice.Builder(
|
||||
Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
|
||||
)
|
||||
.addText(
|
||||
).addText(
|
||||
providerDisplayName, null, listOf(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME))
|
||||
.addIcon(
|
||||
Icon.createWithResource(context, R.drawable.ic_passkey),
|
||||
@@ -384,7 +415,9 @@ class CredentialManagerRepo(
|
||||
return Entry(
|
||||
key,
|
||||
subkey,
|
||||
slice
|
||||
slice,
|
||||
pendingIntent,
|
||||
fillInIntent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -415,7 +448,6 @@ class CredentialManagerRepo(
|
||||
}
|
||||
|
||||
private fun testGetRequestInfo(): RequestInfo {
|
||||
val data = Bundle()
|
||||
return RequestInfo.newGetRequestInfo(
|
||||
Binder(),
|
||||
GetCredentialRequest.Builder()
|
||||
|
||||
@@ -16,17 +16,21 @@
|
||||
|
||||
package com.android.credentialmanager
|
||||
|
||||
import android.credentials.ui.RequestInfo
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.android.credentialmanager.common.DialogType
|
||||
import com.android.credentialmanager.common.DialogResult
|
||||
import com.android.credentialmanager.common.ProviderActivityResult
|
||||
import com.android.credentialmanager.common.ResultState
|
||||
import com.android.credentialmanager.createflow.CreateCredentialScreen
|
||||
import com.android.credentialmanager.createflow.CreateCredentialViewModel
|
||||
@@ -39,28 +43,23 @@ class CredentialSelectorActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
CredentialManagerRepo.setup(this, intent)
|
||||
val requestInfo = intent.extras?.getParcelable<RequestInfo>(RequestInfo.EXTRA_REQUEST_INFO)
|
||||
if (requestInfo != null) {
|
||||
val requestType = requestInfo.type
|
||||
setContent {
|
||||
CredentialSelectorTheme {
|
||||
CredentialManagerBottomSheet(requestType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: prototype only code to be removed. In production should exit.
|
||||
setContent {
|
||||
CredentialSelectorTheme {
|
||||
CredentialManagerBottomSheet(RequestInfo.TYPE_CREATE)
|
||||
}
|
||||
val requestInfo = CredentialManagerRepo.getInstance().requestInfo
|
||||
setContent {
|
||||
CredentialSelectorTheme {
|
||||
CredentialManagerBottomSheet(DialogType.toDialogType(requestInfo.type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
@Composable
|
||||
fun CredentialManagerBottomSheet(operationType: String) {
|
||||
val dialogType = DialogType.toDialogType(operationType)
|
||||
fun CredentialManagerBottomSheet(dialogType: DialogType) {
|
||||
val providerActivityResult = remember { mutableStateOf<ProviderActivityResult?>(null) }
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.StartIntentSenderForResult()
|
||||
) {
|
||||
providerActivityResult.value = ProviderActivityResult(it.resultCode, it.data)
|
||||
}
|
||||
when (dialogType) {
|
||||
DialogType.CREATE_PASSKEY -> {
|
||||
val viewModel: CreateCredentialViewModel = viewModel()
|
||||
@@ -68,7 +67,10 @@ class CredentialSelectorActivity : ComponentActivity() {
|
||||
this@CredentialSelectorActivity,
|
||||
onCancel
|
||||
)
|
||||
CreateCredentialScreen(viewModel = viewModel)
|
||||
providerActivityResult.value?.let {
|
||||
viewModel.onProviderActivityResult(it)
|
||||
}
|
||||
CreateCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
|
||||
}
|
||||
DialogType.GET_CREDENTIALS -> {
|
||||
val viewModel: GetCredentialViewModel = viewModel()
|
||||
@@ -76,7 +78,10 @@ class CredentialSelectorActivity : ComponentActivity() {
|
||||
this@CredentialSelectorActivity,
|
||||
onCancel
|
||||
)
|
||||
GetCredentialScreen(viewModel = viewModel)
|
||||
providerActivityResult.value?.let {
|
||||
viewModel.onProviderActivityResult(it)
|
||||
}
|
||||
GetCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
|
||||
}
|
||||
else -> {
|
||||
Log.w("AccountSelector", "Unknown type, not rendering any UI")
|
||||
|
||||
@@ -46,8 +46,15 @@ class GetFlowUtils {
|
||||
val packageManager = context.packageManager
|
||||
return providerDataList.map {
|
||||
// TODO: get from the actual service info
|
||||
val componentName = ComponentName.unflattenFromString(it.providerFlattenedComponentName)
|
||||
var packageName = componentName?.packageName
|
||||
if (componentName == null) {
|
||||
// TODO: Remove once test data is fixed
|
||||
packageName = it.providerFlattenedComponentName
|
||||
}
|
||||
|
||||
val pkgInfo = packageManager
|
||||
.getPackageInfo(it.providerFlattenedComponentName,
|
||||
.getPackageInfo(packageName!!,
|
||||
PackageManager.PackageInfoFlags.of(0))
|
||||
val providerDisplayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString()
|
||||
// TODO: decide what to do when failed to load a provider icon
|
||||
@@ -86,6 +93,8 @@ class GetFlowUtils {
|
||||
providerId = providerId,
|
||||
entryKey = it.key,
|
||||
entrySubkey = it.subkey,
|
||||
pendingIntent = it.pendingIntent,
|
||||
fillInIntent = it.frameworkExtrasIntent,
|
||||
credentialType = credentialEntryUi.credentialType.toString(),
|
||||
credentialTypeDisplayName = credentialEntryUi.credentialTypeDisplayName.toString(),
|
||||
userName = credentialEntryUi.userName.toString(),
|
||||
@@ -113,6 +122,8 @@ class GetFlowUtils {
|
||||
providerId = providerId,
|
||||
entryKey = authEntry.key,
|
||||
entrySubkey = authEntry.subkey,
|
||||
pendingIntent = authEntry.pendingIntent,
|
||||
fillInIntent = authEntry.frameworkExtrasIntent,
|
||||
title = providerDisplayName,
|
||||
icon = providerIcon,
|
||||
)
|
||||
@@ -127,6 +138,8 @@ class GetFlowUtils {
|
||||
providerId = providerId,
|
||||
entryKey = remoteEntry.key,
|
||||
entrySubkey = remoteEntry.subkey,
|
||||
pendingIntent = remoteEntry.pendingIntent,
|
||||
fillInIntent = remoteEntry.frameworkExtrasIntent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -142,6 +155,8 @@ class GetFlowUtils {
|
||||
providerId = providerId,
|
||||
entryKey = it.key,
|
||||
entrySubkey = it.subkey,
|
||||
pendingIntent = it.pendingIntent,
|
||||
fillInIntent = it.frameworkExtrasIntent,
|
||||
title = actionEntryUi.text.toString(),
|
||||
// TODO: gracefully fail
|
||||
icon = actionEntryUi.icon.loadDrawable(context)!!,
|
||||
@@ -214,6 +229,8 @@ class CreateFlowUtils {
|
||||
// TODO: remove fallbacks
|
||||
entryKey = it.key,
|
||||
entrySubkey = it.subkey,
|
||||
pendingIntent = it.pendingIntent,
|
||||
fillInIntent = it.frameworkExtrasIntent,
|
||||
userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
|
||||
credentialTypeIcon = saveEntryUi.credentialTypeIcon?.loadDrawable(context)
|
||||
?: context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
@@ -235,6 +252,8 @@ class CreateFlowUtils {
|
||||
RemoteInfo(
|
||||
entryKey = remoteEntry.key,
|
||||
entrySubkey = remoteEntry.subkey,
|
||||
pendingIntent = remoteEntry.pendingIntent,
|
||||
fillInIntent = remoteEntry.frameworkExtrasIntent,
|
||||
)
|
||||
} else null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.common
|
||||
|
||||
import android.content.Intent
|
||||
|
||||
data class ProviderActivityResult(
|
||||
val resultCode: Int,
|
||||
val data: Intent?,
|
||||
)
|
||||
@@ -3,6 +3,9 @@
|
||||
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
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -50,7 +53,11 @@ import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Compa
|
||||
@Composable
|
||||
fun CreateCredentialScreen(
|
||||
viewModel: CreateCredentialViewModel,
|
||||
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
|
||||
) {
|
||||
val primaryEntryCallback: () -> Unit = {
|
||||
viewModel.onPrimaryCreateOptionInfoSelected(providerActivityLauncher)
|
||||
}
|
||||
val state = rememberModalBottomSheetState(
|
||||
initialValue = ModalBottomSheetValue.Expanded,
|
||||
skipHalfExpanded = true
|
||||
@@ -73,8 +80,8 @@ fun CreateCredentialScreen(
|
||||
requestDisplayInfo = uiState.requestDisplayInfo,
|
||||
providerInfo = uiState.activeEntry?.activeProvider!!,
|
||||
createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
|
||||
onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
|
||||
onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
|
||||
onOptionSelected = primaryEntryCallback,
|
||||
onConfirm = primaryEntryCallback,
|
||||
onCancel = viewModel::onCancel,
|
||||
multiProvider = uiState.enabledProviders.size > 1,
|
||||
onMoreOptionsSelected = viewModel::onMoreOptionsSelected
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package com.android.credentialmanager.createflow
|
||||
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -25,6 +28,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.android.credentialmanager.CredentialManagerRepo
|
||||
import com.android.credentialmanager.common.DialogResult
|
||||
import com.android.credentialmanager.common.ProviderActivityResult
|
||||
import com.android.credentialmanager.common.ResultState
|
||||
|
||||
data class CreateCredentialUiState(
|
||||
@@ -59,7 +63,8 @@ class CreateCredentialViewModel(
|
||||
uiState = uiState.copy(
|
||||
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
|
||||
activeEntry = ActiveEntry(uiState.enabledProviders.first(),
|
||||
uiState.enabledProviders.first().createOptions.first())
|
||||
uiState.enabledProviders.first().createOptions.first()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
throw java.lang.IllegalStateException("Empty provider list.")
|
||||
@@ -70,7 +75,8 @@ class CreateCredentialViewModel(
|
||||
uiState = uiState.copy(
|
||||
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
|
||||
activeEntry = ActiveEntry(getProviderInfoByName(providerName),
|
||||
getProviderInfoByName(providerName).createOptions.first())
|
||||
getProviderInfoByName(providerName).createOptions.first()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -119,22 +125,56 @@ class CreateCredentialViewModel(
|
||||
// TODO: implement the if choose as default or not logic later
|
||||
}
|
||||
|
||||
fun onPrimaryCreateOptionInfoSelected() {
|
||||
val entryKey = uiState.activeEntry?.activeEntryInfo?.entryKey
|
||||
val entrySubkey = uiState.activeEntry?.activeEntryInfo?.entrySubkey
|
||||
Log.d(
|
||||
"Account Selector",
|
||||
"Option selected for creation: " +
|
||||
"{key = $entryKey, subkey = $entrySubkey}"
|
||||
)
|
||||
if (entryKey != null && entrySubkey != null) {
|
||||
fun onPrimaryCreateOptionInfoSelected(
|
||||
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
|
||||
) {
|
||||
val selectedEntry = uiState.activeEntry?.activeEntryInfo
|
||||
if (selectedEntry != null) {
|
||||
val entryKey = selectedEntry.entryKey
|
||||
val entrySubkey = selectedEntry.entrySubkey
|
||||
Log.d(
|
||||
"Account Selector",
|
||||
"Option selected for creation: " +
|
||||
"{key = $entryKey, subkey = $entrySubkey}"
|
||||
)
|
||||
if (selectedEntry.pendingIntent != null) {
|
||||
val intentSenderRequest = IntentSenderRequest.Builder(selectedEntry.pendingIntent)
|
||||
.setFillInIntent(selectedEntry.fillInIntent).build()
|
||||
launcher.launch(intentSenderRequest)
|
||||
} else {
|
||||
CredentialManagerRepo.getInstance().onOptionSelected(
|
||||
uiState.activeEntry?.activeProvider!!.name,
|
||||
entryKey,
|
||||
entrySubkey
|
||||
)
|
||||
dialogResult.value = DialogResult(
|
||||
ResultState.COMPLETE,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
dialogResult.value = DialogResult(
|
||||
ResultState.COMPLETE,
|
||||
)
|
||||
TODO("Gracefully handle illegal state.")
|
||||
}
|
||||
}
|
||||
|
||||
fun onProviderActivityResult(providerActivityResult: ProviderActivityResult) {
|
||||
val entry = uiState.activeEntry?.activeEntryInfo
|
||||
val resultCode = providerActivityResult.resultCode
|
||||
val resultData = providerActivityResult.data
|
||||
val providerId = uiState.activeEntry?.activeProvider!!.name
|
||||
if (entry != null) {
|
||||
Log.d("Account Selector", "Got provider activity result: {provider=" +
|
||||
"$providerId, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
|
||||
"resultCode=$resultCode, resultData=$resultData}"
|
||||
)
|
||||
CredentialManagerRepo.getInstance().onOptionSelected(
|
||||
uiState.activeEntry?.activeProvider!!.name,
|
||||
entryKey,
|
||||
entrySubkey
|
||||
providerId, entry.entryKey, entry.entrySubkey, resultCode, resultData,
|
||||
)
|
||||
} else {
|
||||
TODO("Gracefully handle illegal state.")
|
||||
Log.w("Account Selector",
|
||||
"Illegal state: received a provider result but found no matching entry.")
|
||||
}
|
||||
dialogResult.value = DialogResult(
|
||||
ResultState.COMPLETE,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.credentialmanager.createflow
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
open class ProviderInfo(
|
||||
@@ -42,11 +44,15 @@ class DisabledProviderInfo(
|
||||
open class EntryInfo (
|
||||
val entryKey: String,
|
||||
val entrySubkey: String,
|
||||
val pendingIntent: PendingIntent?,
|
||||
val fillInIntent: Intent?,
|
||||
)
|
||||
|
||||
class CreateOptionInfo(
|
||||
entryKey: String,
|
||||
entrySubkey: String,
|
||||
pendingIntent: PendingIntent?,
|
||||
fillInIntent: Intent?,
|
||||
val userProviderDisplayName: String?,
|
||||
val credentialTypeIcon: Drawable,
|
||||
val profileIcon: Drawable,
|
||||
@@ -54,12 +60,14 @@ class CreateOptionInfo(
|
||||
val passkeyCount: Int?,
|
||||
val totalCredentialCount: Int?,
|
||||
val lastUsedTimeMillis: Long?,
|
||||
) : EntryInfo(entryKey, entrySubkey)
|
||||
) : EntryInfo(entryKey, entrySubkey, pendingIntent, fillInIntent)
|
||||
|
||||
class RemoteInfo(
|
||||
entryKey: String,
|
||||
entrySubkey: String,
|
||||
) : EntryInfo(entryKey, entrySubkey)
|
||||
pendingIntent: PendingIntent?,
|
||||
fillInIntent: Intent?,
|
||||
) : EntryInfo(entryKey, entrySubkey, pendingIntent, fillInIntent)
|
||||
|
||||
data class RequestDisplayInfo(
|
||||
val title: String,
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package com.android.credentialmanager.getflow
|
||||
|
||||
import android.text.TextUtils
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -62,7 +65,11 @@ import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
|
||||
@Composable
|
||||
fun GetCredentialScreen(
|
||||
viewModel: GetCredentialViewModel,
|
||||
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
|
||||
) {
|
||||
val entrySelectionCallback: (EntryInfo) -> Unit = {
|
||||
viewModel.onEntrySelected(it, providerActivityLauncher)
|
||||
}
|
||||
val state = rememberModalBottomSheetState(
|
||||
initialValue = ModalBottomSheetValue.Expanded,
|
||||
skipHalfExpanded = true
|
||||
@@ -75,14 +82,14 @@ fun GetCredentialScreen(
|
||||
GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
|
||||
requestDisplayInfo = uiState.requestDisplayInfo,
|
||||
providerDisplayInfo = uiState.providerDisplayInfo,
|
||||
onEntrySelected = viewModel::onEntrySelected,
|
||||
onEntrySelected = entrySelectionCallback,
|
||||
onCancel = viewModel::onCancel,
|
||||
onMoreOptionSelected = viewModel::onMoreOptionSelected,
|
||||
)
|
||||
GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
|
||||
providerInfoList = uiState.providerInfoList,
|
||||
providerDisplayInfo = uiState.providerDisplayInfo,
|
||||
onEntrySelected = viewModel::onEntrySelected,
|
||||
onEntrySelected = entrySelectionCallback,
|
||||
onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package com.android.credentialmanager.getflow
|
||||
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -25,6 +28,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.android.credentialmanager.CredentialManagerRepo
|
||||
import com.android.credentialmanager.common.DialogResult
|
||||
import com.android.credentialmanager.common.ProviderActivityResult
|
||||
import com.android.credentialmanager.common.ResultState
|
||||
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
|
||||
import com.android.internal.util.Preconditions
|
||||
@@ -34,6 +38,7 @@ data class GetCredentialUiState(
|
||||
val currentScreenState: GetScreenState,
|
||||
val requestDisplayInfo: RequestDisplayInfo,
|
||||
val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
|
||||
val selectedEntry: EntryInfo? = null,
|
||||
)
|
||||
|
||||
class GetCredentialViewModel(
|
||||
@@ -51,14 +56,42 @@ class GetCredentialViewModel(
|
||||
return dialogResult
|
||||
}
|
||||
|
||||
fun onEntrySelected(entry: EntryInfo) {
|
||||
fun onEntrySelected(
|
||||
entry: EntryInfo,
|
||||
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
|
||||
) {
|
||||
Log.d("Account Selector", "credential selected:" +
|
||||
" {provider=${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}}")
|
||||
CredentialManagerRepo.getInstance().onOptionSelected(
|
||||
entry.providerId,
|
||||
entry.entryKey,
|
||||
entry.entrySubkey
|
||||
)
|
||||
if (entry.pendingIntent != null) {
|
||||
uiState = uiState.copy(selectedEntry = entry)
|
||||
val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
|
||||
.setFillInIntent(entry.fillInIntent).build()
|
||||
launcher.launch(intentSenderRequest)
|
||||
} else {
|
||||
CredentialManagerRepo.getInstance().onOptionSelected(
|
||||
entry.providerId, entry.entryKey, entry.entrySubkey,
|
||||
)
|
||||
dialogResult.value = DialogResult(ResultState.COMPLETE)
|
||||
}
|
||||
}
|
||||
|
||||
fun onProviderActivityResult(providerActivityResult: ProviderActivityResult) {
|
||||
val entry = uiState.selectedEntry
|
||||
val resultCode = providerActivityResult.resultCode
|
||||
val resultData = providerActivityResult.data
|
||||
if (entry != null) {
|
||||
Log.d("Account Selector", "Got provider activity result: {provider=" +
|
||||
"${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
|
||||
"resultCode=$resultCode, resultData=$resultData}"
|
||||
)
|
||||
CredentialManagerRepo.getInstance().onOptionSelected(
|
||||
entry.providerId, entry.entryKey, entry.entrySubkey,
|
||||
resultCode, resultData,
|
||||
)
|
||||
} else {
|
||||
Log.w("Account Selector",
|
||||
"Illegal state: received a provider result but found no matching entry.")
|
||||
}
|
||||
dialogResult.value = DialogResult(ResultState.COMPLETE)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.credentialmanager.getflow
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
data class ProviderInfo(
|
||||
@@ -49,12 +51,16 @@ abstract class EntryInfo (
|
||||
val providerId: String,
|
||||
val entryKey: String,
|
||||
val entrySubkey: String,
|
||||
val pendingIntent: PendingIntent?,
|
||||
val fillInIntent: Intent?,
|
||||
)
|
||||
|
||||
class CredentialEntryInfo(
|
||||
providerId: String,
|
||||
entryKey: String,
|
||||
entrySubkey: String,
|
||||
pendingIntent: PendingIntent?,
|
||||
fillInIntent: Intent?,
|
||||
/** Type of this credential used for sorting. Not localized so must not be directly displayed. */
|
||||
val credentialType: String,
|
||||
/** Localized type value of this credential used for display purpose. */
|
||||
@@ -63,30 +69,36 @@ class CredentialEntryInfo(
|
||||
val displayName: String?,
|
||||
val icon: Drawable,
|
||||
val lastUsedTimeMillis: Long?,
|
||||
) : EntryInfo(providerId, entryKey, entrySubkey)
|
||||
) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
|
||||
|
||||
class AuthenticationEntryInfo(
|
||||
providerId: String,
|
||||
entryKey: String,
|
||||
entrySubkey: String,
|
||||
pendingIntent: PendingIntent?,
|
||||
fillInIntent: Intent?,
|
||||
val title: String,
|
||||
val icon: Drawable,
|
||||
) : EntryInfo(providerId, entryKey, entrySubkey)
|
||||
) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
|
||||
|
||||
class RemoteEntryInfo(
|
||||
providerId: String,
|
||||
entryKey: String,
|
||||
entrySubkey: String,
|
||||
) : EntryInfo(providerId, entryKey, entrySubkey)
|
||||
pendingIntent: PendingIntent?,
|
||||
fillInIntent: Intent?,
|
||||
) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
|
||||
|
||||
class ActionEntryInfo(
|
||||
providerId: String,
|
||||
entryKey: String,
|
||||
entrySubkey: String,
|
||||
pendingIntent: PendingIntent?,
|
||||
fillInIntent: Intent?,
|
||||
val title: String,
|
||||
val icon: Drawable,
|
||||
val subTitle: String?,
|
||||
) : EntryInfo(providerId, entryKey, entrySubkey)
|
||||
) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
|
||||
|
||||
data class RequestDisplayInfo(
|
||||
val appDomainName: String,
|
||||
|
||||
Reference in New Issue
Block a user