Handle finishing up a dialog.

Includes propagating the result back to the service and closing the
activity, when the user cancels (cancel code) or selects an entry
(success code + request token + provider chosen + entry chosen)

Test: deployed locally
Bug: 247855226
Change-Id: Ibbe2fa183d12eb43093aa1191b2691244f58e00c
This commit is contained in:
Helen Qin
2022-10-18 06:18:31 +00:00
parent 281eb31fee
commit a85ae625b3
10 changed files with 161 additions and 29 deletions

View File

@@ -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<Entry> 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<Entry> credentialEntries,
@NonNull List<Entry> 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<Entry> 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);

View File

@@ -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);
}

View File

@@ -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<ProviderData>
// 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 {

View File

@@ -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<DialogResult> {
if (it.resultState == ResultState.COMPLETE || it.resultState == ResultState.CANCELED) {
this@CredentialSelectorActivity.finish()
}
}
}

View File

@@ -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),

View File

@@ -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,
)

View File

@@ -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()
}
}
}

View File

@@ -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<ProviderInfo>,
@@ -36,6 +40,14 @@ class CreatePasskeyViewModel(
var uiState by mutableStateOf(credManRepo.createPasskeyInitialUiState())
private set
val dialogResult: MutableLiveData<DialogResult> by lazy {
MutableLiveData<DialogResult>()
}
fun observeDialogResult(): LiveData<DialogResult> {
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)
}
}

View File

@@ -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()
}
}
}

View File

@@ -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<ProviderInfo>,
@@ -36,11 +40,31 @@ class GetCredentialViewModel(
var uiState by mutableStateOf(credManRepo.getCredentialInitialUiState())
private set
val dialogResult: MutableLiveData<DialogResult> by lazy {
MutableLiveData<DialogResult>()
}
fun observeDialogResult(): LiveData<DialogResult> {
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)
}
}