diff options
| author | 2022-11-21 16:15:38 +0000 | |
|---|---|---|
| committer | 2022-11-21 16:15:38 +0000 | |
| commit | afe6bcfa5521ef9f71981a03638c46097faee24f (patch) | |
| tree | b1e0aa164e3d1d771a571818ac6c4f515e496b6a | |
| parent | c814f2131c781f9f3dbaaccf71fae4536f406909 (diff) | |
| parent | 3e94495777f96db43a189ed02226638c78a9ab16 (diff) | |
Merge "Launch provider pending intent from the CredentialManager UI."
10 files changed, 247 insertions, 60 deletions
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 7e69987f1bf8..a6e64cefedcb 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -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() diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index 1041a33333b3..d324f875c85a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -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") diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index fad9364e3c23..bea1e5943461 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -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 } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ProviderActivityResult.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ProviderActivityResult.kt new file mode 100644 index 000000000000..9e33d51371dd --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ProviderActivityResult.kt @@ -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?, +)
\ 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 27d366d55937..3a277a638e93 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -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 diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt index 6be019fa0882..093c88f1c020 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt @@ -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,23 +125,57 @@ 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) { - CredentialManagerRepo.getInstance().onOptionSelected( - uiState.activeEntry?.activeProvider!!.name, - entryKey, - entrySubkey + 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( + 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, ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 1ab234a0e0bc..753dc3c63bc7 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -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, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 19a032fcf4b8..db0c16c0bb9e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -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, ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt index 22370a91cde4..6dea9c299989 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt @@ -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) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index 76d9847f5356..0c3baffd8864 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -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, |