Add CredentialDescription get impl.
Bug: 260629338 CTS-Coverage-Bug: 265212839 API-Coverage-Bug: 265212839 Test: Local Build & Deployment Change-Id: I44b4fba89cee00b51455d3ddf8696477b98045e6
This commit is contained in:
@@ -19,35 +19,72 @@ package com.android.server.credentials;
|
||||
import android.credentials.CredentialDescription;
|
||||
import android.credentials.RegisterCredentialDescriptionRequest;
|
||||
import android.credentials.UnregisterCredentialDescriptionRequest;
|
||||
import android.service.credentials.CredentialEntry;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/** Contains information on what CredentialProvider has what provisioned Credential. */
|
||||
public class CredentialDescriptionRegistry {
|
||||
public final class CredentialDescriptionRegistry {
|
||||
|
||||
private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
|
||||
private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16;
|
||||
private static SparseArray<CredentialDescriptionRegistry> sCredentialDescriptionSessionPerUser;
|
||||
@GuardedBy("sLock")
|
||||
private static final SparseArray<CredentialDescriptionRegistry>
|
||||
sCredentialDescriptionSessionPerUser;
|
||||
private static final ReentrantLock sLock;
|
||||
|
||||
static {
|
||||
sCredentialDescriptionSessionPerUser = new SparseArray<>();
|
||||
sLock = new ReentrantLock();
|
||||
}
|
||||
|
||||
// TODO(b/265992655): add a way to update CredentialRegistry when a user is removed.
|
||||
/** Get and/or create a {@link CredentialDescription} for the given user id. */
|
||||
public static CredentialDescriptionRegistry forUser(int userId) {
|
||||
CredentialDescriptionRegistry session =
|
||||
sCredentialDescriptionSessionPerUser.get(userId, null);
|
||||
/** Represents the results of a given query into the registry. */
|
||||
public static final class FilterResult {
|
||||
final String mPackageName;
|
||||
final List<CredentialEntry> mCredentialEntries;
|
||||
|
||||
if (session == null) {
|
||||
session = new CredentialDescriptionRegistry();
|
||||
sCredentialDescriptionSessionPerUser.put(userId, session);
|
||||
private FilterResult(String packageName,
|
||||
List<CredentialEntry> credentialEntries) {
|
||||
mPackageName = packageName;
|
||||
mCredentialEntries = credentialEntries;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get and/or create a {@link CredentialDescription} for the given user id. */
|
||||
@GuardedBy("sLock")
|
||||
public static CredentialDescriptionRegistry forUser(int userId) {
|
||||
sLock.lock();
|
||||
try {
|
||||
CredentialDescriptionRegistry session =
|
||||
sCredentialDescriptionSessionPerUser.get(userId, null);
|
||||
|
||||
if (session == null) {
|
||||
session = new CredentialDescriptionRegistry();
|
||||
sCredentialDescriptionSessionPerUser.put(userId, session);
|
||||
}
|
||||
return session;
|
||||
} finally {
|
||||
sLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears an existing session for a given user identifier. */
|
||||
@GuardedBy("sLock")
|
||||
public static void clearUserSession(int userId) {
|
||||
sLock.lock();
|
||||
try {
|
||||
sCredentialDescriptionSessionPerUser.remove(userId);
|
||||
} finally {
|
||||
sLock.unlock();
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
|
||||
@@ -74,7 +111,7 @@ public class CredentialDescriptionRegistry {
|
||||
int size = mCredentialDescriptions.get(callingPackageName).size();
|
||||
mCredentialDescriptions.get(callingPackageName)
|
||||
.addAll(descriptions);
|
||||
mTotalDescriptionCount += size - mCredentialDescriptions.get(callingPackageName).size();
|
||||
mTotalDescriptionCount += mCredentialDescriptions.get(callingPackageName).size() - size;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -93,21 +130,33 @@ public class CredentialDescriptionRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns package names and entries of a CredentialProviders that can satisfy a given
|
||||
* {@link CredentialDescription}. */
|
||||
public Set<FilterResult> getFilteredResultForProvider(String packageName,
|
||||
List<String> flatRequestStrings) {
|
||||
Set<FilterResult> result = new HashSet<>();
|
||||
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
|
||||
for (CredentialDescription containedDescription: currentSet) {
|
||||
if (flatRequestStrings.contains(containedDescription.getFlattenedRequestString())) {
|
||||
result.add(new FilterResult(packageName, containedDescription
|
||||
.getCredentialEntries()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns package names of CredentialProviders that can satisfy a given
|
||||
* {@link CredentialDescription}. */
|
||||
public Set<String> filterCredentials(String flatRequestString) {
|
||||
|
||||
public Set<String> getMatchingProviders(Set<String> flatRequestString) {
|
||||
Set<String> result = new HashSet<>();
|
||||
|
||||
for (String componentName: mCredentialDescriptions.keySet()) {
|
||||
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(componentName);
|
||||
for (CredentialDescription containedDescription: currentSet) {
|
||||
if (flatRequestString.equals(containedDescription.getFlattenedRequestString())) {
|
||||
result.add(componentName);
|
||||
for (String packageName: mCredentialDescriptions.keySet()) {
|
||||
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
|
||||
for (CredentialDescription containedDescription : currentSet) {
|
||||
if (flatRequestString.contains(containedDescription.getFlattenedRequestString())) {
|
||||
result.add(packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,13 +61,11 @@ import com.android.server.infra.AbstractMasterSystemService;
|
||||
import com.android.server.infra.SecureSettingsServiceNameResolver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Entry point service for credential management.
|
||||
@@ -236,6 +234,7 @@ public final class CredentialManagerService
|
||||
concatenatedServices.addAll(getOrConstructSystemServiceListLock(userId));
|
||||
return concatenatedServices;
|
||||
}
|
||||
|
||||
public static boolean isCredentialDescriptionApiEnabled() {
|
||||
return DeviceConfig.getBoolean(
|
||||
DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
|
||||
@@ -244,44 +243,38 @@ public final class CredentialManagerService
|
||||
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
|
||||
// to be guarded by 'service.mLock', which is the same as mLock.
|
||||
private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
|
||||
RequestSession session,
|
||||
List<String> requestOptions, Set<ComponentName> activeCredentialContainers) {
|
||||
GetRequestSession session,
|
||||
List<String> requestOptions, Set<String> activeCredentialContainers) {
|
||||
List<ProviderSession> providerSessions = new ArrayList<>();
|
||||
// Invoke all services of a user to initiate a provider session
|
||||
runForUser((service) -> {
|
||||
if (activeCredentialContainers.contains(service.getComponentName())) {
|
||||
ProviderSession providerSession = service
|
||||
.initiateProviderSessionForRequestLocked(session, requestOptions);
|
||||
if (providerSession != null) {
|
||||
providerSessions.add(providerSession);
|
||||
}
|
||||
}
|
||||
});
|
||||
for (String packageName: activeCredentialContainers) {
|
||||
providerSessions.add(ProviderRegistryGetSession.createNewSession(
|
||||
mContext,
|
||||
UserHandle.getCallingUserId(),
|
||||
session,
|
||||
packageName,
|
||||
requestOptions));
|
||||
}
|
||||
return providerSessions;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Set<String> getMatchingProviders(GetCredentialRequest request) {
|
||||
private Set<String> getFilteredResultFromRegistry(List<CredentialOption> options) {
|
||||
// Session for active/provisioned credential descriptions;
|
||||
CredentialDescriptionRegistry registry = CredentialDescriptionRegistry
|
||||
.forUser(UserHandle.getCallingUserId());
|
||||
|
||||
// All requested credential descriptions based on the given request.
|
||||
Set<String> requestedCredentialDescriptions =
|
||||
request.getCredentialOptions().stream().map(
|
||||
credentialOption -> credentialOption
|
||||
options.stream().map(
|
||||
getCredentialOption -> getCredentialOption
|
||||
.getCredentialRetrievalData()
|
||||
.getString(CredentialOption
|
||||
.FLATTENED_REQUEST))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// All requested credential descriptions based on the given request.
|
||||
return requestedCredentialDescriptions.stream()
|
||||
.map(registry::filterCredentials)
|
||||
.flatMap(
|
||||
(Function<Set<String>, Stream<String>>)
|
||||
Collection::stream)
|
||||
.collect(Collectors.toSet());
|
||||
return registry.getMatchingProviders(requestedCredentialDescriptions);
|
||||
}
|
||||
|
||||
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
|
||||
@@ -304,6 +297,13 @@ public final class CredentialManagerService
|
||||
return providerSessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
@GuardedBy("CredentialDescriptionRegistry.sLock")
|
||||
public void onUserStopped(@NonNull TargetUser user) {
|
||||
super.onUserStopped(user);
|
||||
CredentialDescriptionRegistry.clearUserSession(user.getUserIdentifier());
|
||||
}
|
||||
|
||||
private CallingAppInfo constructCallingAppInfo(String packageName, int userId) {
|
||||
final PackageInfo packageInfo;
|
||||
try {
|
||||
@@ -340,13 +340,57 @@ public final class CredentialManagerService
|
||||
request,
|
||||
constructCallingAppInfo(callingPackage, userId));
|
||||
|
||||
// Initiate all provider sessions
|
||||
List<ProviderSession> providerSessions =
|
||||
initiateProviderSessions(
|
||||
session,
|
||||
request.getCredentialOptions().stream()
|
||||
.map(CredentialOption::getType)
|
||||
.collect(Collectors.toList()));
|
||||
List<ProviderSession> providerSessions;
|
||||
|
||||
if (isCredentialDescriptionApiEnabled()) {
|
||||
List<CredentialOption> optionsThatRequireActiveCredentials =
|
||||
request.getCredentialOptions().stream()
|
||||
.filter(getCredentialOption ->
|
||||
!TextUtils.isEmpty(getCredentialOption
|
||||
.getCredentialRetrievalData().getString(
|
||||
CredentialOption
|
||||
.FLATTENED_REQUEST, null)))
|
||||
.toList();
|
||||
|
||||
List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
|
||||
request.getCredentialOptions().stream()
|
||||
.filter(getCredentialOption ->
|
||||
TextUtils.isEmpty(getCredentialOption
|
||||
.getCredentialRetrievalData().getString(
|
||||
CredentialOption
|
||||
.FLATTENED_REQUEST, null)))
|
||||
.toList();
|
||||
|
||||
List<ProviderSession> sessionsWithoutRemoteService =
|
||||
initiateProviderSessionsWithActiveContainers(session,
|
||||
optionsThatRequireActiveCredentials
|
||||
.stream().map(getCredentialOption ->
|
||||
getCredentialOption.getCredentialRetrievalData()
|
||||
.getString(CredentialOption
|
||||
.FLATTENED_REQUEST))
|
||||
.collect(Collectors.toList()),
|
||||
getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
|
||||
|
||||
List<ProviderSession> sessionsWithRemoteService = initiateProviderSessions(
|
||||
session,
|
||||
optionsThatDoNotRequireActiveCredentials.stream()
|
||||
.map(CredentialOption::getType)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
Set<ProviderSession> all = new LinkedHashSet<>();
|
||||
all.addAll(sessionsWithRemoteService);
|
||||
all.addAll(sessionsWithoutRemoteService);
|
||||
|
||||
providerSessions = new ArrayList<>(all);
|
||||
} else {
|
||||
// Initiate all provider sessions
|
||||
providerSessions =
|
||||
initiateProviderSessions(
|
||||
session,
|
||||
request.getCredentialOptions().stream()
|
||||
.map(CredentialOption::getType)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
if (providerSessions.isEmpty()) {
|
||||
try {
|
||||
@@ -363,6 +407,7 @@ public final class CredentialManagerService
|
||||
|
||||
// Iterate over all provider sessions and invoke the request
|
||||
providerSessions.forEach(ProviderSession::invokeSession);
|
||||
|
||||
return cancelTransport;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,8 +118,8 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS
|
||||
|
||||
@Override
|
||||
protected void invokeSession() {
|
||||
this.mRemoteCredentialService.onClearCredentialState(
|
||||
this.getProviderRequest(),
|
||||
/*callback=*/this);
|
||||
if (mRemoteCredentialService != null) {
|
||||
mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ public final class ProviderCreateSession extends ProviderSession<
|
||||
|
||||
// Key to be used as an entry key for a save entry
|
||||
private static final String SAVE_ENTRY_KEY = "save_entry_key";
|
||||
// Key to be used as an entry key for a remote entry
|
||||
private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
|
||||
|
||||
@NonNull
|
||||
private final Map<String, CreateEntry> mUiSaveEntries = new HashMap<>();
|
||||
@@ -199,9 +201,9 @@ public final class ProviderCreateSession extends ProviderSession<
|
||||
|
||||
@Override
|
||||
protected void invokeSession() {
|
||||
this.mRemoteCredentialService.onCreateCredential(
|
||||
this.getProviderRequest(),
|
||||
/*callback=*/this);
|
||||
if (mRemoteCredentialService != null) {
|
||||
mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
|
||||
|
||||
@@ -59,14 +59,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
|
||||
implements
|
||||
RemoteCredentialService.ProviderCallbacks<BeginGetCredentialResponse> {
|
||||
private static final String TAG = "ProviderGetSession";
|
||||
|
||||
// Key to be used as an entry key for a credential entry
|
||||
private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
|
||||
|
||||
// Key to be used as the entry key for an action entry
|
||||
private static final String ACTION_ENTRY_KEY = "action_key";
|
||||
// Key to be used as the entry key for the authentication entry
|
||||
private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
|
||||
// Key to be used as an entry key for a remote entry
|
||||
private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
|
||||
// Key to be used as an entry key for a credential entry
|
||||
private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
|
||||
|
||||
@NonNull
|
||||
private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
|
||||
@@ -101,23 +101,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
|
||||
return null;
|
||||
}
|
||||
|
||||
private static BeginGetCredentialRequest constructQueryPhaseRequest(
|
||||
android.credentials.GetCredentialRequest filteredRequest,
|
||||
CallingAppInfo callingAppInfo
|
||||
) {
|
||||
return new BeginGetCredentialRequest.Builder(callingAppInfo)
|
||||
.setBeginGetCredentialOptions(
|
||||
filteredRequest.getCredentialOptions().stream().map(
|
||||
option -> {
|
||||
return new BeginGetCredentialOption(
|
||||
option.getType(),
|
||||
option.getCandidateQueryData());
|
||||
}).collect(Collectors.toList()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static android.credentials.GetCredentialRequest filterOptions(
|
||||
protected static android.credentials.GetCredentialRequest filterOptions(
|
||||
List<String> providerCapabilities,
|
||||
android.credentials.GetCredentialRequest clientRequest
|
||||
) {
|
||||
@@ -142,6 +127,21 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
|
||||
return null;
|
||||
}
|
||||
|
||||
private static BeginGetCredentialRequest constructQueryPhaseRequest(
|
||||
android.credentials.GetCredentialRequest filteredRequest,
|
||||
CallingAppInfo callingAppInfo
|
||||
) {
|
||||
return new BeginGetCredentialRequest.Builder(callingAppInfo)
|
||||
.setBeginGetCredentialOptions(
|
||||
filteredRequest.getCredentialOptions().stream().map(
|
||||
option -> {
|
||||
return new BeginGetCredentialOption(
|
||||
option.getType(),
|
||||
option.getCandidateQueryData());
|
||||
}).collect(Collectors.toList()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public ProviderGetSession(Context context,
|
||||
CredentialProviderInfo info,
|
||||
ProviderInternalCallback<GetCredentialResponse> callbacks,
|
||||
@@ -232,9 +232,9 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
|
||||
|
||||
@Override
|
||||
protected void invokeSession() {
|
||||
this.mRemoteCredentialService.onBeginGetCredential(
|
||||
this.getProviderRequest(),
|
||||
/*callback=*/this);
|
||||
if (mRemoteCredentialService != null) {
|
||||
mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override // Call from request session to data to be shown on the UI
|
||||
@@ -379,6 +379,28 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
|
||||
invokeCallbackOnInternalInvalidState();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected GetCredentialException maybeGetPendingIntentException(
|
||||
ProviderPendingIntentResponse pendingIntentResponse) {
|
||||
if (pendingIntentResponse == null) {
|
||||
Log.i(TAG, "pendingIntentResponse is null");
|
||||
return null;
|
||||
}
|
||||
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
|
||||
GetCredentialException exception = PendingIntentResultHandler
|
||||
.extractGetCredentialException(pendingIntentResponse.getResultData());
|
||||
if (exception != null) {
|
||||
Log.i(TAG, "Pending intent contains provider exception");
|
||||
return exception;
|
||||
}
|
||||
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
|
||||
return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED);
|
||||
} else {
|
||||
return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void onAuthenticationEntrySelected(
|
||||
@Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
|
||||
//TODO: Other provider intent statuses
|
||||
@@ -431,28 +453,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
|
||||
updateStatusAndInvokeCallback(Status.NO_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private GetCredentialException maybeGetPendingIntentException(
|
||||
ProviderPendingIntentResponse pendingIntentResponse) {
|
||||
if (pendingIntentResponse == null) {
|
||||
Log.i(TAG, "pendingIntentResponse is null");
|
||||
return null;
|
||||
}
|
||||
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
|
||||
GetCredentialException exception = PendingIntentResultHandler
|
||||
.extractGetCredentialException(pendingIntentResponse.getResultData());
|
||||
if (exception != null) {
|
||||
Log.i(TAG, "Pending intent contains provider exception");
|
||||
return exception;
|
||||
}
|
||||
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
|
||||
return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED);
|
||||
} else {
|
||||
return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* When an invalid state occurs, e.g. entry mismatch or no response from provider,
|
||||
* we send back a TYPE_UNKNOWN error as to the developer.
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.server.credentials;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.credentials.CredentialOption;
|
||||
import android.credentials.GetCredentialException;
|
||||
import android.credentials.GetCredentialRequest;
|
||||
import android.credentials.GetCredentialResponse;
|
||||
import android.credentials.ui.Entry;
|
||||
import android.credentials.ui.GetCredentialProviderData;
|
||||
import android.credentials.ui.ProviderData;
|
||||
import android.credentials.ui.ProviderPendingIntentResponse;
|
||||
import android.service.credentials.CallingAppInfo;
|
||||
import android.service.credentials.CredentialEntry;
|
||||
import android.service.credentials.CredentialProviderService;
|
||||
import android.telecom.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Central provider session that utilizes {@link CredentialDescriptionRegistry} and therefor is able
|
||||
* to bypass having to use a {@link RemoteCredentialService}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class ProviderRegistryGetSession extends ProviderSession<GetCredentialRequest,
|
||||
Set<CredentialDescriptionRegistry.FilterResult>> {
|
||||
|
||||
private static final String TAG = "ProviderRegistryGetSession";
|
||||
private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
|
||||
|
||||
/** Creates a new provider session to be used by the request session. */
|
||||
@Nullable
|
||||
public static ProviderRegistryGetSession createNewSession(
|
||||
@NonNull Context context,
|
||||
@UserIdInt int userId,
|
||||
@NonNull GetRequestSession getRequestSession,
|
||||
@NonNull String credentialProviderPackageName,
|
||||
@NonNull List<String> requestOptions) {
|
||||
return new ProviderRegistryGetSession(
|
||||
context,
|
||||
userId,
|
||||
getRequestSession,
|
||||
getRequestSession.mClientRequest,
|
||||
getRequestSession.mClientAppInfo,
|
||||
credentialProviderPackageName,
|
||||
requestOptions);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
|
||||
@NonNull
|
||||
private final CredentialDescriptionRegistry mCredentialDescriptionRegistry;
|
||||
@NonNull
|
||||
private final CallingAppInfo mCallingAppInfo;
|
||||
@NonNull
|
||||
private final String mCredentialProviderPackageName;
|
||||
@NonNull
|
||||
private final GetRequestSession mGetRequestSession;
|
||||
@NonNull
|
||||
private final List<String> mRequestOptions;
|
||||
private List<CredentialEntry> mCredentialEntries;
|
||||
|
||||
protected ProviderRegistryGetSession(@NonNull Context context,
|
||||
@NonNull int userId,
|
||||
@NonNull GetRequestSession session,
|
||||
@NonNull GetCredentialRequest request,
|
||||
@NonNull CallingAppInfo callingAppInfo,
|
||||
@NonNull String servicePackageName,
|
||||
@NonNull List<String> requestOptions) {
|
||||
super(context, null, request, session, userId, null);
|
||||
mGetRequestSession = session;
|
||||
mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
|
||||
mCallingAppInfo = callingAppInfo;
|
||||
mCredentialProviderPackageName = servicePackageName;
|
||||
mRequestOptions = requestOptions;
|
||||
}
|
||||
|
||||
private List<Entry> prepareUiCredentialEntries(
|
||||
@NonNull List<CredentialEntry> credentialEntries) {
|
||||
Log.i(TAG, "in prepareUiProviderDataWithCredentials");
|
||||
List<Entry> credentialUiEntries = new ArrayList<>();
|
||||
|
||||
// Populate the credential entries
|
||||
for (CredentialEntry credentialEntry : credentialEntries) {
|
||||
String entryId = generateEntryId();
|
||||
mUiCredentialEntries.put(entryId, credentialEntry);
|
||||
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
|
||||
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
|
||||
credentialEntry.getSlice(),
|
||||
setUpFillInIntent(credentialEntry.getType())));
|
||||
}
|
||||
return credentialUiEntries;
|
||||
}
|
||||
|
||||
private Intent setUpFillInIntent(String type) {
|
||||
Intent intent = new Intent();
|
||||
for (CredentialOption option : mProviderRequest.getCredentialOptions()) {
|
||||
if (option.getType().equals(type)) {
|
||||
intent.putExtra(
|
||||
CredentialProviderService
|
||||
.EXTRA_GET_CREDENTIAL_REQUEST,
|
||||
new android.service.credentials.GetCredentialRequest(
|
||||
mCallingAppInfo, option));
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProviderData prepareUiData() {
|
||||
Log.i(TAG, "In prepareUiData");
|
||||
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
|
||||
Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
|
||||
+ mComponentName.flattenToString());
|
||||
return null;
|
||||
}
|
||||
if (mProviderResponse == null) {
|
||||
Log.i(TAG, "In prepareUiData response null");
|
||||
throw new IllegalStateException("Response must be in completion mode");
|
||||
}
|
||||
return new GetCredentialProviderData.Builder(
|
||||
mComponentName.flattenToString()).setActionChips(null)
|
||||
.setCredentialEntries(prepareUiCredentialEntries(
|
||||
mProviderResponse.stream().flatMap((Function<CredentialDescriptionRegistry
|
||||
.FilterResult,
|
||||
Stream<CredentialEntry>>) filterResult ->
|
||||
filterResult.mCredentialEntries.stream())
|
||||
.collect(Collectors.toList())))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override // Selection call from the request provider
|
||||
protected void onUiEntrySelected(String entryType, String entryKey,
|
||||
ProviderPendingIntentResponse providerPendingIntentResponse) {
|
||||
switch (entryType) {
|
||||
case CREDENTIAL_ENTRY_KEY:
|
||||
CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
|
||||
if (credentialEntry == null) {
|
||||
Log.i(TAG, "Unexpected credential entry key");
|
||||
return;
|
||||
}
|
||||
onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
|
||||
break;
|
||||
default:
|
||||
Log.i(TAG, "Unsupported entry type selected");
|
||||
}
|
||||
}
|
||||
|
||||
private void onCredentialEntrySelected(CredentialEntry credentialEntry,
|
||||
ProviderPendingIntentResponse providerPendingIntentResponse) {
|
||||
if (!mCredentialEntries.contains(credentialEntry)) {
|
||||
invokeCallbackWithError("",
|
||||
"");
|
||||
}
|
||||
|
||||
if (providerPendingIntentResponse != null) {
|
||||
// Check if pending intent has an error
|
||||
GetCredentialException exception = maybeGetPendingIntentException(
|
||||
providerPendingIntentResponse);
|
||||
if (exception != null) {
|
||||
invokeCallbackWithError(exception.getType(),
|
||||
exception.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if pending intent has a credential
|
||||
GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
|
||||
.extractGetCredentialResponse(
|
||||
providerPendingIntentResponse.getResultData());
|
||||
if (getCredentialResponse != null) {
|
||||
if (mCallbacks != null) {
|
||||
mCallbacks.onFinalResponseReceived(mComponentName,
|
||||
getCredentialResponse);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Pending intent response contains no credential, or error");
|
||||
}
|
||||
Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderResponseSuccess(
|
||||
@Nullable Set<CredentialDescriptionRegistry.FilterResult> response) {
|
||||
// No need to do anything since this class does not rely on a remote service.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderResponseFailure(int internalErrorCode, @Nullable Exception e) {
|
||||
// No need to do anything since this class does not rely on a remote service.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderServiceDied(RemoteCredentialService service) {
|
||||
// No need to do anything since this class does not rely on a remote service.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invokeSession() {
|
||||
mProviderResponse = mCredentialDescriptionRegistry
|
||||
.getFilteredResultForProvider(mCredentialProviderPackageName,
|
||||
mRequestOptions);
|
||||
mCredentialEntries = mProviderResponse.stream().flatMap(
|
||||
(Function<CredentialDescriptionRegistry.FilterResult,
|
||||
Stream<CredentialEntry>>) filterResult
|
||||
-> filterResult.mCredentialEntries.stream())
|
||||
.collect(Collectors.toList());
|
||||
setStatus(Status.CREDENTIALS_RECEIVED);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected GetCredentialException maybeGetPendingIntentException(
|
||||
ProviderPendingIntentResponse pendingIntentResponse) {
|
||||
if (pendingIntentResponse == null) {
|
||||
android.util.Log.i(TAG, "pendingIntentResponse is null");
|
||||
return null;
|
||||
}
|
||||
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
|
||||
GetCredentialException exception = PendingIntentResultHandler
|
||||
.extractGetCredentialException(pendingIntentResponse.getResultData());
|
||||
if (exception != null) {
|
||||
android.util.Log.i(TAG, "Pending intent contains provider exception");
|
||||
return exception;
|
||||
}
|
||||
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
|
||||
return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED);
|
||||
} else {
|
||||
return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -38,16 +38,14 @@ public abstract class ProviderSession<T, R>
|
||||
implements RemoteCredentialService.ProviderCallbacks<R> {
|
||||
|
||||
private static final String TAG = "ProviderSession";
|
||||
// Key to be used as an entry key for a remote entry
|
||||
protected static final String REMOTE_ENTRY_KEY = "remote_entry_key";
|
||||
|
||||
@NonNull protected final Context mContext;
|
||||
@NonNull protected final ComponentName mComponentName;
|
||||
@NonNull protected final CredentialProviderInfo mProviderInfo;
|
||||
@NonNull protected final RemoteCredentialService mRemoteCredentialService;
|
||||
@Nullable protected final CredentialProviderInfo mProviderInfo;
|
||||
@Nullable protected final RemoteCredentialService mRemoteCredentialService;
|
||||
@NonNull protected final int mUserId;
|
||||
@NonNull protected Status mStatus = Status.NOT_STARTED;
|
||||
@NonNull protected final ProviderInternalCallback mCallbacks;
|
||||
@Nullable protected final ProviderInternalCallback mCallbacks;
|
||||
@Nullable protected Credential mFinalCredentialResponse;
|
||||
@NonNull protected final T mProviderRequest;
|
||||
@Nullable protected R mProviderResponse;
|
||||
@@ -109,9 +107,9 @@ public abstract class ProviderSession<T, R>
|
||||
|
||||
protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
|
||||
@NonNull T providerRequest,
|
||||
@NonNull ProviderInternalCallback callbacks,
|
||||
@Nullable ProviderInternalCallback callbacks,
|
||||
@NonNull int userId,
|
||||
@NonNull RemoteCredentialService remoteCredentialService) {
|
||||
@Nullable RemoteCredentialService remoteCredentialService) {
|
||||
mContext = context;
|
||||
mProviderInfo = info;
|
||||
mProviderRequest = providerRequest;
|
||||
@@ -165,7 +163,7 @@ public abstract class ProviderSession<T, R>
|
||||
return mComponentName;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Nullable
|
||||
protected RemoteCredentialService getRemoteCredentialService() {
|
||||
return mRemoteCredentialService;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user