diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java index 18e6ba4305893..38bd4e57b758d 100644 --- a/core/java/android/credentials/ui/ProviderData.java +++ b/core/java/android/credentials/ui/ProviderData.java @@ -41,7 +41,7 @@ public class ProviderData implements Parcelable { "android.credentials.ui.extra.PROVIDER_DATA_LIST"; @NonNull - private final String mPackageName; + private final String mProviderId; @NonNull private final List mCredentialEntries; @NonNull @@ -50,11 +50,11 @@ public class ProviderData implements Parcelable { private final Entry mAuthenticationEntry; public ProviderData( - @NonNull String packageName, + @NonNull String providerId, @NonNull List credentialEntries, @NonNull List actionChips, @Nullable Entry authenticationEntry) { - mPackageName = packageName; + mProviderId = providerId; mCredentialEntries = credentialEntries; mActionChips = actionChips; mAuthenticationEntry = authenticationEntry; @@ -62,8 +62,8 @@ public class ProviderData implements Parcelable { /** Returns the provider package name. */ @NonNull - public String getPackageName() { - return mPackageName; + public String getProviderId() { + return mProviderId; } @NonNull @@ -82,9 +82,9 @@ public class ProviderData implements Parcelable { } protected ProviderData(@NonNull Parcel in) { - String packageName = in.readString8(); - mPackageName = packageName; - AnnotationValidations.validate(NonNull.class, null, mPackageName); + String providerId = in.readString8(); + mProviderId = providerId; + AnnotationValidations.validate(NonNull.class, null, mProviderId); List credentialEntries = new ArrayList<>(); in.readTypedList(credentialEntries, Entry.CREATOR); @@ -102,7 +102,7 @@ public class ProviderData implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString8(mPackageName); + dest.writeString8(mProviderId); dest.writeTypedList(mCredentialEntries); dest.writeTypedList(mActionChips); dest.writeTypedObject(mAuthenticationEntry, flags); diff --git a/core/java/android/credentials/ui/UserSelectionResult.java b/core/java/android/credentials/ui/UserSelectionResult.java index 0927fb830a0bb..2ac559381c6ed 100644 --- a/core/java/android/credentials/ui/UserSelectionResult.java +++ b/core/java/android/credentials/ui/UserSelectionResult.java @@ -43,11 +43,16 @@ public class UserSelectionResult implements Parcelable { @NonNull private final IBinder mRequestToken; + @NonNull + private final String mProviderId; + // TODO: consider switching to string or other types, depending on the service implementation. private final int mEntryId; - public UserSelectionResult(@NonNull IBinder requestToken, int entryId) { + public UserSelectionResult(@NonNull IBinder requestToken, @NonNull String providerId, + int entryId) { mRequestToken = requestToken; + mProviderId = providerId; mEntryId = entryId; } @@ -57,23 +62,33 @@ public class UserSelectionResult implements Parcelable { return mRequestToken; } + /** Returns provider package name whose entry was selected by the user. */ + @NonNull + public String getProviderId() { + return mProviderId; + } + /** Returns the id of the visual entry that the user selected. */ - public int geEntryId() { + public int getEntryId() { return mEntryId; } protected UserSelectionResult(@NonNull Parcel in) { IBinder requestToken = in.readStrongBinder(); + String providerId = in.readString8(); int entryId = in.readInt(); mRequestToken = requestToken; AnnotationValidations.validate(NonNull.class, null, mRequestToken); + mProviderId = providerId; + AnnotationValidations.validate(NonNull.class, null, mProviderId); mEntryId = entryId; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeStrongBinder(mRequestToken); + dest.writeString8(mProviderId); dest.writeInt(mEntryId); } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index c575fbc6b7f89..489cc27913706 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -16,6 +16,7 @@ package com.android.credentialmanager +import android.app.Activity import android.app.slice.Slice import android.app.slice.SliceSpec import android.content.Context @@ -23,8 +24,11 @@ import android.content.Intent import android.credentials.ui.Entry import android.credentials.ui.ProviderData import android.credentials.ui.RequestInfo +import android.credentials.ui.UserSelectionResult import android.graphics.drawable.Icon import android.os.Binder +import android.os.Bundle +import android.os.ResultReceiver import com.android.credentialmanager.createflow.CreatePasskeyUiState import com.android.credentialmanager.createflow.CreateScreenState import com.android.credentialmanager.getflow.GetCredentialUiState @@ -37,6 +41,8 @@ class CredentialManagerRepo( ) { private val requestInfo: RequestInfo private val providerList: List + // TODO: require non-null. + val resultReceiver: ResultReceiver? init { requestInfo = intent.extras?.getParcelable( @@ -52,6 +58,29 @@ class CredentialManagerRepo( ProviderData.EXTRA_PROVIDER_DATA_LIST, ProviderData::class.java ) ?: testProviderList() + + resultReceiver = intent.getParcelableExtra( + RequestInfo.EXTRA_RESULT_RECEIVER, + ResultReceiver::class.java + ) + } + + fun onCancel() { + resultReceiver?.send(Activity.RESULT_CANCELED, null) + } + + fun onOptionSelected(providerPackageName: String, entryId: Int) { + val userSelectionResult = UserSelectionResult( + requestInfo.token, + providerPackageName, + entryId + ) + val resultData = Bundle() + resultData.putParcelable( + UserSelectionResult.EXTRA_USER_SELECTION_RESULT, + userSelectionResult + ) + resultReceiver?.send(Activity.RESULT_OK, resultData) } fun getCredentialInitialUiState(): GetCredentialUiState { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index b538ae79091fc..78edaa936bcdb 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -23,9 +23,15 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable +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.ResultState import com.android.credentialmanager.createflow.CreatePasskeyScreen +import com.android.credentialmanager.createflow.CreatePasskeyViewModel import com.android.credentialmanager.getflow.GetCredentialScreen +import com.android.credentialmanager.getflow.GetCredentialViewModel import com.android.credentialmanager.ui.theme.CredentialSelectorTheme @ExperimentalMaterialApi @@ -57,10 +63,20 @@ class CredentialSelectorActivity : ComponentActivity() { val dialogType = DialogType.toDialogType(operationType) when (dialogType) { DialogType.CREATE_PASSKEY -> { - CreatePasskeyScreen(cancelActivity = onCancel) + val viewModel: CreatePasskeyViewModel = viewModel() + viewModel.observeDialogResult().observe( + this@CredentialSelectorActivity, + onCancel + ) + CreatePasskeyScreen(viewModel = viewModel) } DialogType.GET_CREDENTIALS -> { - GetCredentialScreen(cancelActivity = onCancel) + val viewModel: GetCredentialViewModel = viewModel() + viewModel.observeDialogResult().observe( + this@CredentialSelectorActivity, + onCancel + ) + GetCredentialScreen(viewModel = viewModel) } else -> { Log.w("AccountSelector", "Unknown type, not rendering any UI") @@ -69,7 +85,9 @@ class CredentialSelectorActivity : ComponentActivity() { } } - private val onCancel = { - this@CredentialSelectorActivity.finish() + private val onCancel = Observer { + if (it.resultState == ResultState.COMPLETE || it.resultState == ResultState.CANCELED) { + this@CredentialSelectorActivity.finish() + } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index af27ce504641c..6b503ff9c10b1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -35,7 +35,7 @@ class GetFlowUtils { ProviderInfo( // TODO: replace to extract from the service data structure when available icon = context.getDrawable(R.drawable.ic_passkey)!!, - name = it.packageName, + name = it.providerId, appDomainName = "tribank.us", credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!, credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context) @@ -78,7 +78,7 @@ class CreateFlowUtils { com.android.credentialmanager.createflow.ProviderInfo( // TODO: replace to extract from the service data structure when available icon = context.getDrawable(R.drawable.ic_passkey)!!, - name = it.packageName, + name = it.providerId, appDomainName = "tribank.us", credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!, createOptions = toCreationOptionInfoList(it.credentialEntries, context), diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt new file mode 100644 index 0000000000000..b75166347c56b --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt @@ -0,0 +1,26 @@ +/* + * 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 + +enum class ResultState { + COMPLETE, + CANCELED, +} + +data class DialogResult( + val resultState: ResultState, +) \ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt index 6489d73285668..f4d60b5f77ccd 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt @@ -38,7 +38,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap -import androidx.lifecycle.viewmodel.compose.viewModel import com.android.credentialmanager.R import com.android.credentialmanager.ui.theme.Grey100 import com.android.credentialmanager.ui.theme.Shapes @@ -50,8 +49,7 @@ import com.android.credentialmanager.ui.theme.lightSurface1 @ExperimentalMaterialApi @Composable fun CreatePasskeyScreen( - viewModel: CreatePasskeyViewModel = viewModel(), - cancelActivity: () -> Unit, + viewModel: CreatePasskeyViewModel, ) { val state = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Expanded, @@ -64,17 +62,17 @@ fun CreatePasskeyScreen( when (uiState.currentScreenState) { CreateScreenState.PASSKEY_INTRO -> ConfirmationCard( onConfirm = {viewModel.onConfirmIntro()}, - onCancel = cancelActivity, + onCancel = {viewModel.onCancel()}, ) CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard( providerList = uiState.providers, - onCancel = cancelActivity, + onCancel = {viewModel.onCancel()}, onProviderSelected = {viewModel.onProviderSelected(it)} ) CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard( providerInfo = uiState.selectedProvider!!, onOptionSelected = {viewModel.onCreateOptionSelected(it)}, - onCancel = cancelActivity, + onCancel = {viewModel.onCancel()}, multiProvider = uiState.providers.size > 1, onMoreOptionsSelected = {viewModel.onMoreOptionsSelected(it)} ) @@ -93,7 +91,7 @@ fun CreatePasskeyScreen( ) {} LaunchedEffect(state.currentValue) { if (state.currentValue == ModalBottomSheetValue.Hidden) { - cancelActivity() + viewModel.onCancel() } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt index 85fe31d312abc..3cf81da1fbaa8 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt @@ -20,8 +20,12 @@ import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.android.credentialmanager.CredentialManagerRepo +import com.android.credentialmanager.common.DialogResult +import com.android.credentialmanager.common.ResultState data class CreatePasskeyUiState( val providers: List, @@ -36,6 +40,14 @@ class CreatePasskeyViewModel( var uiState by mutableStateOf(credManRepo.createPasskeyInitialUiState()) private set + val dialogResult: MutableLiveData by lazy { + MutableLiveData() + } + + fun observeDialogResult(): LiveData { + return dialogResult + } + fun onConfirmIntro() { if (uiState.providers.size > 1) { uiState = uiState.copy( @@ -60,6 +72,13 @@ class CreatePasskeyViewModel( fun onCreateOptionSelected(createOptionId: Int) { Log.d("Account Selector", "Option selected for creation: $createOptionId") + CredentialManagerRepo.getInstance().onOptionSelected( + uiState.selectedProvider!!.name, + createOptionId + ) + dialogResult.value = DialogResult( + ResultState.COMPLETE, + ) } fun getProviderInfoByName(providerName: String): ProviderInfo { @@ -88,4 +107,9 @@ class CreatePasskeyViewModel( selectedProvider = getProviderInfoByName(providerName) ) } + + fun onCancel() { + CredentialManagerRepo.getInstance().onCancel() + dialogResult.value = DialogResult(ResultState.CANCELED) + } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 0b188221e8b3b..48c67bb8770d4 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -44,7 +44,6 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap -import androidx.lifecycle.viewmodel.compose.viewModel import com.android.credentialmanager.R import com.android.credentialmanager.createflow.CancelButton import com.android.credentialmanager.ui.theme.Grey100 @@ -55,8 +54,7 @@ import com.android.credentialmanager.ui.theme.lightBackgroundColor @ExperimentalMaterialApi @Composable fun GetCredentialScreen( - viewModel: GetCredentialViewModel = viewModel(), - cancelActivity: () -> Unit, + viewModel: GetCredentialViewModel, ) { val state = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Expanded, @@ -69,7 +67,7 @@ fun GetCredentialScreen( when (uiState.currentScreenState) { GetScreenState.CREDENTIAL_SELECTION -> CredentialSelectionCard( providerInfo = uiState.selectedProvider!!, - onCancel = cancelActivity, + onCancel = {viewModel.onCancel()}, onOptionSelected = {viewModel.onCredentailSelected(it)}, multiProvider = uiState.providers.size > 1, onMoreOptionSelected = {viewModel.onMoreOptionSelected()}, @@ -81,7 +79,7 @@ fun GetCredentialScreen( ) {} LaunchedEffect(state.currentValue) { if (state.currentValue == ModalBottomSheetValue.Hidden) { - cancelActivity() + viewModel.onCancel() } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt index 0fdd8ecd38c9e..33858f523e3c9 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt @@ -20,8 +20,12 @@ import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.android.credentialmanager.CredentialManagerRepo +import com.android.credentialmanager.common.DialogResult +import com.android.credentialmanager.common.ResultState data class GetCredentialUiState( val providers: List, @@ -36,11 +40,31 @@ class GetCredentialViewModel( var uiState by mutableStateOf(credManRepo.getCredentialInitialUiState()) private set + val dialogResult: MutableLiveData by lazy { + MutableLiveData() + } + + fun observeDialogResult(): LiveData { + return dialogResult + } + fun onCredentailSelected(credentialId: Int) { Log.d("Account Selector", "credential selected: $credentialId") + CredentialManagerRepo.getInstance().onOptionSelected( + uiState.selectedProvider!!.name, + credentialId + ) + dialogResult.value = DialogResult( + ResultState.COMPLETE, + ) } fun onMoreOptionSelected() { Log.d("Account Selector", "More Option selected") } + + fun onCancel() { + CredentialManagerRepo.getInstance().onCancel() + dialogResult.value = DialogResult(ResultState.CANCELED) + } }