Merge "Add request to authentication entry pending intent"
This commit is contained in:
committed by
Android (Google) Code Review
commit
8b0fad3c9a
@@ -39948,6 +39948,7 @@ package android.service.credentials {
|
||||
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
|
||||
method public abstract void onClearCredentialState(@NonNull android.service.credentials.ClearCredentialStateRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
|
||||
field public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
|
||||
field public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST";
|
||||
field public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
|
||||
field public static final String EXTRA_CREATE_CREDENTIAL_REQUEST = "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
|
||||
field public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE = "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
|
||||
|
||||
@@ -88,6 +88,7 @@ public final class Entry implements Parcelable {
|
||||
/** Constructor to be used for an entry that requires a pending intent to be invoked
|
||||
* when clicked.
|
||||
*/
|
||||
// TODO: Remove this constructor as it is no longer used
|
||||
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
|
||||
@NonNull PendingIntent pendingIntent, @NonNull Intent intent) {
|
||||
this(key, subkey, slice);
|
||||
|
||||
@@ -125,6 +125,33 @@ public abstract class CredentialProviderService extends Service {
|
||||
public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION =
|
||||
"android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
|
||||
|
||||
/**
|
||||
* Intent extra: The {@link BeginGetCredentialRequest} attached with
|
||||
* the {@code pendingIntent} that is invoked when the user selects an
|
||||
* authentication entry (intending to unlock the provider app) on the UI.
|
||||
*
|
||||
* <p>When a provider app receives a {@link BeginGetCredentialRequest} through the
|
||||
* {@link CredentialProviderService#onBeginGetCredential} call, it can construct the
|
||||
* {@link BeginGetCredentialResponse} with either an authentication {@link Action} (if the app
|
||||
* is locked), or a {@link CredentialsResponseContent} (if the app is unlocked). In the former
|
||||
* case, i.e. the app is locked, user will be shown the authentication action. When selected,
|
||||
* the underlying {@link PendingIntent} will be invoked which will lead the user to provider's
|
||||
* unlock activity. This pending intent will also contain the original
|
||||
* {@link BeginGetCredentialRequest} to be retrieved and processed after the unlock
|
||||
* flow is complete.
|
||||
*
|
||||
* <p>After the app is unlocked, the {@link BeginGetCredentialResponse} must be constructed
|
||||
* using a {@link CredentialsResponseContent}, which must be set on an {@link Intent} as an
|
||||
* intent extra against CredentialProviderService#EXTRA_CREDENTIALS_RESPONSE_CONTENT}.
|
||||
* This intent should then be set as a result through {@link android.app.Activity#setResult}
|
||||
* before finishing the activity.
|
||||
*
|
||||
* <p>
|
||||
* Type: {@link BeginGetCredentialRequest}
|
||||
*/
|
||||
public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST =
|
||||
"android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST";
|
||||
|
||||
private static final String TAG = "CredProviderService";
|
||||
|
||||
public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
|
||||
|
||||
@@ -44,6 +44,7 @@ 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.CredentialCountInformation
|
||||
import com.android.credentialmanager.jetpack.provider.CredentialEntry
|
||||
import com.android.credentialmanager.jetpack.provider.CreateEntry
|
||||
@@ -140,16 +141,20 @@ class GetFlowUtils {
|
||||
providerIcon: Drawable,
|
||||
authEntry: Entry?,
|
||||
): AuthenticationEntryInfo? {
|
||||
// TODO: should also call fromSlice after getting the official jetpack code.
|
||||
|
||||
if (authEntry == null) {
|
||||
return null
|
||||
}
|
||||
val authStructuredEntry = AuthenticationAction.fromSlice(
|
||||
authEntry!!.slice)
|
||||
if (authStructuredEntry == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return AuthenticationEntryInfo(
|
||||
providerId = providerId,
|
||||
entryKey = authEntry.key,
|
||||
entrySubkey = authEntry.subkey,
|
||||
pendingIntent = authEntry.pendingIntent,
|
||||
pendingIntent = authStructuredEntry.pendingIntent,
|
||||
fillInIntent = authEntry.frameworkExtrasIntent,
|
||||
title = providerDisplayName,
|
||||
icon = providerIcon,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,14 +84,12 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta
|
||||
respondToClientWithResponseAndFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProviderResponseComplete(ComponentName componentName) {
|
||||
if (!isAnyProviderPending()) {
|
||||
onFinalResponseReceived(componentName, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProviderTerminated(ComponentName componentName) {
|
||||
if (!isAnyProviderPending()) {
|
||||
processResponses();
|
||||
|
||||
@@ -86,12 +86,6 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStatusChanged(ProviderSession.Status status,
|
||||
ComponentName componentName) {
|
||||
super.onProviderStatusChanged(status, componentName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinalResponseReceived(ComponentName componentName,
|
||||
@Nullable CreateCredentialResponse response) {
|
||||
@@ -136,4 +130,22 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
|
||||
}
|
||||
finishSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStatusChanged(ProviderSession.Status status,
|
||||
ComponentName componentName) {
|
||||
Log.i(TAG, "in onProviderStatusChanged with status: " + status);
|
||||
// If all provider responses have been received, we can either need the UI,
|
||||
// or we need to respond with error. The only other case is the entry being
|
||||
// selected after the UI has been invoked which has a separate code path.
|
||||
if (!isAnyProviderPending()) {
|
||||
if (isUiInvocationNeeded()) {
|
||||
Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
|
||||
getProviderDataAndInitiateUi();
|
||||
} else {
|
||||
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREDENTIAL,
|
||||
"No credentials available");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,12 +81,6 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from provider session
|
||||
public void onProviderStatusChanged(ProviderSession.Status status,
|
||||
ComponentName componentName) {
|
||||
super.onProviderStatusChanged(status, componentName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinalResponseReceived(ComponentName componentName,
|
||||
@Nullable GetCredentialResponse response) {
|
||||
@@ -132,4 +126,22 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
|
||||
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
|
||||
"User cancelled the selector");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStatusChanged(ProviderSession.Status status,
|
||||
ComponentName componentName) {
|
||||
Log.i(TAG, "in onStatusChanged with status: " + status);
|
||||
if (!isAnyProviderPending()) {
|
||||
// If all provider responses have been received, we can either need the UI,
|
||||
// or we need to respond with error. The only other case is the entry being
|
||||
// selected after the UI has been invoked which has a separate code path.
|
||||
if (isUiInvocationNeeded()) {
|
||||
Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
|
||||
getProviderDataAndInitiateUi();
|
||||
} else {
|
||||
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
|
||||
"No credentials available");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +275,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
|
||||
String entryId = generateEntryId();
|
||||
Entry authEntry = new Entry(
|
||||
AUTHENTICATION_ACTION_ENTRY_KEY, entryId,
|
||||
authenticationAction.getSlice());
|
||||
authenticationAction.getSlice(),
|
||||
setUpFillInIntentForAuthentication());
|
||||
mUiAuthenticationAction = new Pair<>(entryId, authenticationAction);
|
||||
return authEntry;
|
||||
}
|
||||
@@ -311,6 +312,15 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
|
||||
return intent;
|
||||
}
|
||||
|
||||
private Intent setUpFillInIntentForAuthentication() {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(
|
||||
CredentialProviderService
|
||||
.EXTRA_BEGIN_GET_CREDENTIAL_REQUEST,
|
||||
mProviderRequest);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private List<Entry> prepareUiActionEntries(@Nullable List<Action> actions) {
|
||||
List<Entry> actionEntries = new ArrayList<>();
|
||||
for (Action action : actions) {
|
||||
@@ -365,7 +375,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
|
||||
private void onAuthenticationEntrySelected(
|
||||
@Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
|
||||
//TODO: Other provider intent statuses
|
||||
// Check if pending intent has an error
|
||||
GetCredentialException exception = maybeGetPendingIntentException(
|
||||
providerPendingIntentResponse);
|
||||
if (exception != null) {
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.android.server.credentials;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.credentials.ui.ProviderData;
|
||||
import android.credentials.ui.UserSelectionDialogResult;
|
||||
@@ -98,41 +97,6 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
|
||||
finishSession();
|
||||
}
|
||||
|
||||
protected void onProviderStatusChanged(ProviderSession.Status status,
|
||||
ComponentName componentName) {
|
||||
Log.i(TAG, "in onStatusChanged with status: " + status);
|
||||
if (ProviderSession.isTerminatingStatus(status)) {
|
||||
Log.i(TAG, "in onStatusChanged terminating status");
|
||||
onProviderTerminated(componentName);
|
||||
//TODO: Check if this was the provider we were waiting for and can invoke the UI now
|
||||
} else if (ProviderSession.isCompletionStatus(status)) {
|
||||
Log.i(TAG, "in onStatusChanged isCompletionStatus status");
|
||||
onProviderResponseComplete(componentName);
|
||||
} else if (ProviderSession.isUiInvokingStatus(status)) {
|
||||
Log.i(TAG, "in onStatusChanged isUiInvokingStatus status");
|
||||
onProviderResponseRequiresUi();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onProviderTerminated(ComponentName componentName) {
|
||||
//TODO: Implement
|
||||
}
|
||||
|
||||
protected void onProviderResponseComplete(ComponentName componentName) {
|
||||
//TODO: Implement
|
||||
}
|
||||
|
||||
protected void onProviderResponseRequiresUi() {
|
||||
Log.i(TAG, "in onProviderResponseComplete");
|
||||
// TODO: Determine whether UI has already been invoked, and deal accordingly
|
||||
if (!isAnyProviderPending()) {
|
||||
Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
|
||||
getProviderDataAndInitiateUi();
|
||||
} else {
|
||||
Log.i(TAG, "Can't invoke UI - waiting on some providers");
|
||||
}
|
||||
}
|
||||
|
||||
protected void finishSession() {
|
||||
Log.i(TAG, "finishing session");
|
||||
clearProviderSessions();
|
||||
@@ -144,7 +108,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
|
||||
mProviders.clear();
|
||||
}
|
||||
|
||||
boolean isAnyProviderPending() {
|
||||
protected boolean isAnyProviderPending() {
|
||||
for (ProviderSession session : mProviders.values()) {
|
||||
if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
|
||||
return true;
|
||||
@@ -153,7 +117,22 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
|
||||
return false;
|
||||
}
|
||||
|
||||
private void getProviderDataAndInitiateUi() {
|
||||
/**
|
||||
* Returns true if at least one provider is ready for UI invocation, and no
|
||||
* provider is pending a response.
|
||||
*/
|
||||
boolean isUiInvocationNeeded() {
|
||||
for (ProviderSession session : mProviders.values()) {
|
||||
if (ProviderSession.isUiInvokingStatus(session.getStatus())) {
|
||||
return true;
|
||||
} else if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void getProviderDataAndInitiateUi() {
|
||||
Log.i(TAG, "In getProviderDataAndInitiateUi");
|
||||
Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user