Merge "Basic create flow for credential manager up until pending intent is invoked Test: Built locally & deployed Bug: 253155340"
This commit is contained in:
@@ -173,7 +173,7 @@ public final class CredentialEntry implements Parcelable {
|
||||
*/
|
||||
public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
|
||||
if (pendingIntent != null) {
|
||||
Preconditions.checkState(mCredential != null,
|
||||
Preconditions.checkState(mCredential == null,
|
||||
"credential is already set. Cannot set both the pendingIntent "
|
||||
+ "and the credential");
|
||||
}
|
||||
@@ -189,7 +189,7 @@ public final class CredentialEntry implements Parcelable {
|
||||
*/
|
||||
public @NonNull Builder setCredential(@Nullable Credential credential) {
|
||||
if (credential != null) {
|
||||
Preconditions.checkState(mPendingIntent != null,
|
||||
Preconditions.checkState(mPendingIntent == null,
|
||||
"pendingIntent is already set. Cannot set both the "
|
||||
+ "pendingIntent and the credential");
|
||||
}
|
||||
@@ -215,10 +215,10 @@ public final class CredentialEntry implements Parcelable {
|
||||
* is set, or if both are set.
|
||||
*/
|
||||
public @NonNull CredentialEntry build() {
|
||||
Preconditions.checkState(mPendingIntent == null && mCredential == null,
|
||||
"Either pendingIntent or credential must be set");
|
||||
Preconditions.checkState(mPendingIntent != null && mCredential != null,
|
||||
"Cannot set both the pendingIntent and credential");
|
||||
Preconditions.checkState(((mPendingIntent != null && mCredential == null)
|
||||
|| (mPendingIntent == null && mCredential != null)),
|
||||
"Either pendingIntent or credential must be set, and both cannot"
|
||||
+ "be set at the same time");
|
||||
return new CredentialEntry(mType, mSlice, mPendingIntent,
|
||||
mCredential, mAutoSelectAllowed);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.app.AppGlobals;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageItemInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
@@ -96,6 +95,8 @@ public final class CredentialProviderInfo {
|
||||
mLabel = mServiceInfo.loadSafeLabel(
|
||||
mContext.getPackageManager(), 0 /* do not ellipsize */,
|
||||
TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
|
||||
Log.i(TAG, "mLabel is : " + mLabel + ", for: " + mServiceInfo.getComponentName()
|
||||
.flattenToString());
|
||||
populateProviderCapabilities(context, serviceInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,18 @@ import java.util.Objects;
|
||||
* @hide
|
||||
*/
|
||||
public abstract class CredentialProviderService extends Service {
|
||||
/** Extra to be used by provider to populate the credential when ending the activity started
|
||||
* through the {@code pendingIntent} on the selected {@link SaveEntry}. **/
|
||||
public static final String EXTRA_SAVE_CREDENTIAL =
|
||||
"android.service.credentials.extra.SAVE_CREDENTIAL";
|
||||
|
||||
/**
|
||||
* Provider must read the value against this extra to receive the complete create credential
|
||||
* request parameters, when a pending intent is launched.
|
||||
*/
|
||||
public static final String EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS =
|
||||
"android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST_PARAMS";
|
||||
|
||||
private static final String TAG = "CredProviderService";
|
||||
|
||||
public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
|
||||
@@ -64,7 +76,7 @@ public abstract class CredentialProviderService extends Service {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @NonNull IBinder onBind(@NonNull Intent intent) {
|
||||
@NonNull public final IBinder onBind(@NonNull Intent intent) {
|
||||
if (SERVICE_INTERFACE.equals(intent.getAction())) {
|
||||
return mInterface.asBinder();
|
||||
}
|
||||
|
||||
@@ -119,9 +119,9 @@ public final class GetCredentialsRequest implements Parcelable {
|
||||
*/
|
||||
public @NonNull Builder setGetCredentialOptions(
|
||||
@NonNull List<GetCredentialOption> getCredentialOptions) {
|
||||
Preconditions.checkCollectionNotEmpty(mGetCredentialOptions,
|
||||
Preconditions.checkCollectionNotEmpty(getCredentialOptions,
|
||||
"getCredentialOptions");
|
||||
Preconditions.checkCollectionElementsNotNull(mGetCredentialOptions,
|
||||
Preconditions.checkCollectionElementsNotNull(getCredentialOptions,
|
||||
"getCredentialOptions");
|
||||
mGetCredentialOptions = getCredentialOptions;
|
||||
return this;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.server.credentials;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.credentials.CreateCredentialRequest;
|
||||
import android.credentials.CredentialManager;
|
||||
import android.credentials.ICreateCredentialCallback;
|
||||
import android.credentials.ui.ProviderData;
|
||||
import android.credentials.ui.RequestInfo;
|
||||
import android.service.credentials.CredentialProviderInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Central session for a single {@link CredentialManager#executeCreateCredential} request.
|
||||
* This class listens to the responses from providers, and the UX app, and updates the
|
||||
* provider(s) state maintained in {@link ProviderCreateSession}.
|
||||
*/
|
||||
public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
|
||||
ICreateCredentialCallback> {
|
||||
private static final String TAG = "CreateRequestSession";
|
||||
|
||||
CreateRequestSession(@NonNull Context context, int userId,
|
||||
CreateCredentialRequest request,
|
||||
ICreateCredentialCallback callback,
|
||||
String callingPackage) {
|
||||
super(context, userId, request, callback, RequestInfo.TYPE_CREATE, callingPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new provider session, and adds it to list of providers that are contributing to
|
||||
* this request session.
|
||||
*
|
||||
* @return the provider session that was started
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
|
||||
RemoteCredentialService remoteCredentialService) {
|
||||
ProviderCreateSession providerCreateSession = ProviderCreateSession
|
||||
.createNewSession(mContext, mUserId, providerInfo,
|
||||
this, remoteCredentialService);
|
||||
if (providerCreateSession != null) {
|
||||
Log.i(TAG, "In startProviderSession - provider session created and being added");
|
||||
mProviders.put(providerCreateSession.getComponentName().flattenToString(),
|
||||
providerCreateSession);
|
||||
}
|
||||
return providerCreateSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
|
||||
mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newCreateRequestInfo(
|
||||
mRequestId, mClientRequest, mIsFirstUiTurn, mClientCallingPackage),
|
||||
providerDataList));
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.credentials.CreateCredentialRequest;
|
||||
import android.credentials.GetCredentialOption;
|
||||
import android.credentials.GetCredentialRequest;
|
||||
import android.credentials.IClearCredentialSessionCallback;
|
||||
import android.credentials.ICreateCredentialCallback;
|
||||
@@ -33,6 +34,7 @@ import android.os.CancellationSignal;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.service.credentials.GetCredentialsRequest;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
@@ -43,6 +45,7 @@ import com.android.server.infra.SecureSettingsServiceNameResolver;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Entry point service for credential management.
|
||||
@@ -91,17 +94,15 @@ public final class CredentialManagerService extends
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<CredentialManagerServiceImpl> serviceList = new ArrayList<>(serviceNames.length);
|
||||
for (int i = 0; i < serviceNames.length; i++) {
|
||||
Log.i(TAG, "in newServiceListLocked, service: " + serviceNames[i]);
|
||||
if (TextUtils.isEmpty(serviceNames[i])) {
|
||||
for (String serviceName : serviceNames) {
|
||||
Log.i(TAG, "in newServiceListLocked, service: " + serviceName);
|
||||
if (TextUtils.isEmpty(serviceName)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
serviceList.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
|
||||
serviceNames[i]));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
|
||||
} catch (SecurityException e) {
|
||||
serviceName));
|
||||
} catch (PackageManager.NameNotFoundException | SecurityException e) {
|
||||
Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -115,15 +116,31 @@ public final class CredentialManagerService extends
|
||||
synchronized (mLock) {
|
||||
final List<CredentialManagerServiceImpl> services =
|
||||
getServiceListForUserLocked(userId);
|
||||
services.forEach(s -> {
|
||||
for (CredentialManagerServiceImpl s : services) {
|
||||
c.accept(s);
|
||||
});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(origId);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ProviderSession> initiateProviderSessions(RequestSession session,
|
||||
List<String> requestOptions) {
|
||||
List<ProviderSession> providerSessions = new ArrayList<>();
|
||||
// Invoke all services of a user to initiate a provider session
|
||||
runForUser((service) -> {
|
||||
if (service.isServiceCapable(requestOptions)) {
|
||||
ProviderSession providerSession = service
|
||||
.initiateProviderSessionForRequest(session);
|
||||
if (providerSession != null) {
|
||||
providerSessions.add(providerSession);
|
||||
}
|
||||
}
|
||||
});
|
||||
return providerSessions;
|
||||
}
|
||||
|
||||
final class CredentialManagerServiceStub extends ICredentialManager.Stub {
|
||||
@Override
|
||||
public ICancellationSignal executeGetCredential(
|
||||
@@ -137,11 +154,22 @@ public final class CredentialManagerService extends
|
||||
// New request session, scoped for this request only.
|
||||
final GetRequestSession session = new GetRequestSession(getContext(),
|
||||
UserHandle.getCallingUserId(),
|
||||
callback);
|
||||
callback,
|
||||
request,
|
||||
callingPackage);
|
||||
|
||||
// Invoke all services of a user
|
||||
runForUser((service) -> {
|
||||
service.getCredential(request, session, callingPackage);
|
||||
// Initiate all provider sessions
|
||||
List<ProviderSession> providerSessions =
|
||||
initiateProviderSessions(session, request.getGetCredentialOptions()
|
||||
.stream().map(GetCredentialOption::getType)
|
||||
.collect(Collectors.toList()));
|
||||
// TODO : Return error when no providers available
|
||||
|
||||
// Iterate over all provider sessions and invoke the request
|
||||
providerSessions.forEach(providerGetSession -> {
|
||||
providerGetSession.getRemoteCredentialService().onGetCredentials(
|
||||
(GetCredentialsRequest) providerGetSession.getProviderRequest(),
|
||||
/*callback=*/providerGetSession);
|
||||
});
|
||||
return cancelTransport;
|
||||
}
|
||||
@@ -151,9 +179,29 @@ public final class CredentialManagerService extends
|
||||
CreateCredentialRequest request,
|
||||
ICreateCredentialCallback callback,
|
||||
String callingPackage) {
|
||||
// TODO: implement.
|
||||
Log.i(TAG, "executeCreateCredential");
|
||||
Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
|
||||
// TODO : Implement cancellation
|
||||
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
|
||||
|
||||
// New request session, scoped for this request only.
|
||||
final CreateRequestSession session = new CreateRequestSession(getContext(),
|
||||
UserHandle.getCallingUserId(),
|
||||
request,
|
||||
callback,
|
||||
callingPackage);
|
||||
|
||||
// Initiate all provider sessions
|
||||
List<ProviderSession> providerSessions =
|
||||
initiateProviderSessions(session, List.of(request.getType()));
|
||||
// TODO : Return error when no providers available
|
||||
|
||||
// Iterate over all provider sessions and invoke the request
|
||||
providerSessions.forEach(providerCreateSession -> {
|
||||
providerCreateSession.getRemoteCredentialService().onCreateCredential(
|
||||
(android.service.credentials.CreateCredentialRequest)
|
||||
providerCreateSession.getProviderRequest(),
|
||||
/*callback=*/providerCreateSession);
|
||||
});
|
||||
return cancelTransport;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ import android.annotation.Nullable;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.credentials.GetCredentialRequest;
|
||||
import android.service.credentials.CredentialProviderInfo;
|
||||
import android.service.credentials.GetCredentialsRequest;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.infra.AbstractPerUserSystemService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Per-user, per remote service implementation of {@link CredentialManagerService}
|
||||
@@ -61,50 +61,38 @@ public final class CredentialManagerServiceImpl extends
|
||||
return mInfo.getServiceInfo();
|
||||
}
|
||||
|
||||
public void getCredential(GetCredentialRequest request, GetRequestSession requestSession,
|
||||
String callingPackage) {
|
||||
Slog.i(TAG, "in getCredential in CredManServiceImpl");
|
||||
/**
|
||||
* Starts a provider session and associates it with the given request session. */
|
||||
@Nullable
|
||||
public ProviderSession initiateProviderSessionForRequest(
|
||||
RequestSession requestSession) {
|
||||
Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl");
|
||||
if (mInfo == null) {
|
||||
Slog.i(TAG, "in getCredential in CredManServiceImpl, but mInfo is null");
|
||||
return;
|
||||
Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, "
|
||||
+ "but mInfo is null. This shouldn't happen");
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO : Determine if remoteService instance can be reused across requests
|
||||
final RemoteCredentialService remoteService = new RemoteCredentialService(
|
||||
getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
|
||||
ProviderGetSession providerSession = new ProviderGetSession(mInfo,
|
||||
requestSession, mUserId, remoteService);
|
||||
// Set the provider info to the session when the request is initiated. This happens here
|
||||
// because there is one serviceImpl per remote provider, and so we can only retrieve
|
||||
// the provider information in the scope of this instance, whereas the session is for the
|
||||
// entire request.
|
||||
requestSession.addProviderSession(providerSession);
|
||||
GetCredentialsRequest filteredRequest = getRequestWithValidType(request, callingPackage);
|
||||
if (filteredRequest != null) {
|
||||
remoteService.onGetCredentials(getRequestWithValidType(request, callingPackage),
|
||||
providerSession);
|
||||
}
|
||||
ProviderSession providerSession =
|
||||
requestSession.initiateProviderSession(mInfo, remoteService);
|
||||
return providerSession;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private GetCredentialsRequest getRequestWithValidType(GetCredentialRequest request,
|
||||
String callingPackage) {
|
||||
GetCredentialsRequest.Builder builder =
|
||||
new GetCredentialsRequest.Builder(callingPackage);
|
||||
request.getGetCredentialOptions().forEach( option -> {
|
||||
if (mInfo.hasCapability(option.getType())) {
|
||||
Slog.i(TAG, "Provider can handle: " + option.getType());
|
||||
builder.addGetCredentialOption(option);
|
||||
} else {
|
||||
Slog.i(TAG, "Skipping request as provider cannot handle it");
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
return builder.build();
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
Slog.i(TAG, "issue with request build: " + e.getMessage());
|
||||
/** Return true if at least one capability found. */
|
||||
boolean isServiceCapable(List<String> requestedOptions) {
|
||||
if (mInfo == null) {
|
||||
Slog.i(TAG, "in isServiceCapable, mInfo is null");
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
for (String capability : requestedOptions) {
|
||||
if (mInfo.hasCapability(capability)) {
|
||||
Slog.i(TAG, "Provider can handle: " + capability);
|
||||
return true;
|
||||
} else {
|
||||
Slog.i(TAG, "Provider cannot handle: " + capability);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ public class CredentialManagerUi {
|
||||
@NonNull
|
||||
private final CredentialManagerUiCallback mCallbacks;
|
||||
@NonNull private final Context mContext;
|
||||
// TODO : Use for starting the activity for this user
|
||||
private final int mUserId;
|
||||
@NonNull private final ResultReceiver mResultReceiver = new ResultReceiver(
|
||||
new Handler(Looper.getMainLooper())) {
|
||||
@@ -56,7 +57,7 @@ public class CredentialManagerUi {
|
||||
Slog.i(TAG, "No selection found in UI result");
|
||||
}
|
||||
} else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_CANCELED) {
|
||||
mCallbacks.onUiCancelation();
|
||||
mCallbacks.onUiCancellation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ public class CredentialManagerUi {
|
||||
/** Called when the user makes a selection. */
|
||||
void onUiSelection(UserSelectionDialogResult selection);
|
||||
/** Called when the user cancels the UI. */
|
||||
void onUiCancelation();
|
||||
void onUiCancellation();
|
||||
}
|
||||
public CredentialManagerUi(Context context, int userId,
|
||||
CredentialManagerUiCallback callbacks) {
|
||||
@@ -83,9 +84,8 @@ public class CredentialManagerUi {
|
||||
*/
|
||||
public void show(RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
|
||||
Log.i(TAG, "In show");
|
||||
Intent intent = IntentFactory.newIntent(
|
||||
requestInfo, providerDataList,
|
||||
new ArrayList<>(), mResultReceiver);
|
||||
Intent intent = IntentFactory.newIntent(requestInfo, providerDataList, new ArrayList<>(),
|
||||
mResultReceiver);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mContext.startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
package com.android.server.credentials;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.credentials.Credential;
|
||||
import android.credentials.GetCredentialRequest;
|
||||
import android.credentials.GetCredentialResponse;
|
||||
import android.credentials.IGetCredentialCallback;
|
||||
import android.credentials.ui.ProviderData;
|
||||
@@ -26,62 +27,52 @@ import android.credentials.ui.RequestInfo;
|
||||
import android.credentials.ui.UserSelectionDialogResult;
|
||||
import android.os.RemoteException;
|
||||
import android.service.credentials.CredentialEntry;
|
||||
import android.service.credentials.CredentialProviderInfo;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Central session for a single getCredentials request. This class listens to the
|
||||
* responses from providers, and the UX app, and updates the provider(S) state.
|
||||
*/
|
||||
public final class GetRequestSession extends RequestSession {
|
||||
public final class GetRequestSession extends RequestSession<GetCredentialRequest,
|
||||
IGetCredentialCallback> {
|
||||
private static final String TAG = "GetRequestSession";
|
||||
|
||||
private final IGetCredentialCallback mClientCallback;
|
||||
private final Map<String, ProviderGetSession> mProviders;
|
||||
|
||||
public GetRequestSession(Context context, int userId,
|
||||
IGetCredentialCallback callback) {
|
||||
super(context, userId, RequestInfo.TYPE_GET);
|
||||
mClientCallback = callback;
|
||||
mProviders = new HashMap<>();
|
||||
IGetCredentialCallback callback, GetCredentialRequest request,
|
||||
String callingPackage) {
|
||||
super(context, userId, request, callback, RequestInfo.TYPE_GET, callingPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new provider to the list of providers that are contributing to this session.
|
||||
* Creates a new provider session, and adds it list of providers that are contributing to
|
||||
* this session.
|
||||
* @return the provider session created within this request session, for the given provider
|
||||
* info.
|
||||
*/
|
||||
public void addProviderSession(ProviderGetSession providerSession) {
|
||||
mProviders.put(providerSession.getComponentName().flattenToString(),
|
||||
providerSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStatusChanged(ProviderSession.Status status,
|
||||
ComponentName componentName) {
|
||||
Log.i(TAG, "in onStatusChanged");
|
||||
if (ProviderSession.isTerminatingStatus(status)) {
|
||||
Log.i(TAG, "in onStatusChanged terminating status");
|
||||
|
||||
ProviderGetSession session = mProviders.remove(componentName.flattenToString());
|
||||
if (session != null) {
|
||||
Slog.i(TAG, "Provider session removed.");
|
||||
} else {
|
||||
Slog.i(TAG, "Provider session null, did not exist.");
|
||||
}
|
||||
} else if (ProviderSession.isCompletionStatus(status)) {
|
||||
Log.i(TAG, "in onStatusChanged isCompletionStatus status");
|
||||
onProviderResponseComplete();
|
||||
@Nullable
|
||||
public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
|
||||
RemoteCredentialService remoteCredentialService) {
|
||||
ProviderGetSession providerGetSession = ProviderGetSession
|
||||
.createNewSession(mContext, mUserId, providerInfo,
|
||||
this, remoteCredentialService);
|
||||
if (providerGetSession != null) {
|
||||
Log.i(TAG, "In startProviderSession - provider session created and being added");
|
||||
mProviders.put(providerGetSession.getComponentName().flattenToString(),
|
||||
providerGetSession);
|
||||
}
|
||||
return providerGetSession;
|
||||
}
|
||||
|
||||
// TODO: Override for this method not needed once get selection logic is
|
||||
// moved to ProviderGetSession
|
||||
@Override
|
||||
public void onUiSelection(UserSelectionDialogResult selection) {
|
||||
String providerId = selection.getProviderId();
|
||||
ProviderGetSession providerSession = mProviders.get(providerId);
|
||||
ProviderGetSession providerSession = (ProviderGetSession) mProviders.get(providerId);
|
||||
if (providerSession != null) {
|
||||
CredentialEntry credentialEntry = providerSession.getCredentialEntry(
|
||||
selection.getEntrySubkey());
|
||||
@@ -89,57 +80,17 @@ public final class GetRequestSession extends RequestSession {
|
||||
respondToClientAndFinish(credentialEntry.getCredential());
|
||||
}
|
||||
// TODO : Handle action chips and authentication selection
|
||||
return;
|
||||
}
|
||||
// TODO : finish session and respond to client if provider not found
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUiCancelation() {
|
||||
// User canceled the activity
|
||||
// TODO : Send error code to client
|
||||
finishSession();
|
||||
}
|
||||
|
||||
private void onProviderResponseComplete() {
|
||||
Log.i(TAG, "in onProviderResponseComplete");
|
||||
if (isResponseCompleteAcrossProviders()) {
|
||||
Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
|
||||
getProviderDataAndInitiateUi();
|
||||
}
|
||||
}
|
||||
|
||||
private void getProviderDataAndInitiateUi() {
|
||||
ArrayList<ProviderData> providerDataList = new ArrayList<>();
|
||||
for (ProviderGetSession session : mProviders.values()) {
|
||||
Log.i(TAG, "preparing data for : " + session.getComponentName());
|
||||
providerDataList.add(session.prepareUiData());
|
||||
}
|
||||
if (!providerDataList.isEmpty()) {
|
||||
Log.i(TAG, "provider list not empty about to initiate ui");
|
||||
initiateUi(providerDataList);
|
||||
}
|
||||
}
|
||||
|
||||
private void initiateUi(ArrayList<ProviderData> providerDataList) {
|
||||
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
|
||||
mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newGetRequestInfo(
|
||||
mRequestId, null, mIsFirstUiTurn, ""),
|
||||
providerDataList));
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over all provider sessions and returns true if all have responded.
|
||||
*/
|
||||
private boolean isResponseCompleteAcrossProviders() {
|
||||
AtomicBoolean isRequestComplete = new AtomicBoolean(true);
|
||||
mProviders.forEach( (packageName, session) -> {
|
||||
if (session.getStatus() != ProviderSession.Status.COMPLETE) {
|
||||
isRequestComplete.set(false);
|
||||
}
|
||||
});
|
||||
return isRequestComplete.get();
|
||||
}
|
||||
|
||||
private void respondToClientAndFinish(Credential credential) {
|
||||
try {
|
||||
mClientCallback.onResponse(new GetCredentialResponse(credential));
|
||||
@@ -148,17 +99,4 @@ public final class GetRequestSession extends RequestSession {
|
||||
}
|
||||
finishSession();
|
||||
}
|
||||
|
||||
private void finishSession() {
|
||||
clearProviderSessions();
|
||||
}
|
||||
|
||||
private void clearProviderSessions() {
|
||||
for (ProviderGetSession session : mProviders.values()) {
|
||||
// TODO : Evaluate if we should unbind remote services here or wait for them
|
||||
// to automatically unbind when idle. Re-binding frequently also has a cost.
|
||||
//session.destroy();
|
||||
}
|
||||
mProviders.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* 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.server.credentials;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
import android.credentials.Credential;
|
||||
import android.credentials.ui.CreateCredentialProviderData;
|
||||
import android.credentials.ui.Entry;
|
||||
import android.os.Bundle;
|
||||
import android.service.credentials.CreateCredentialRequest;
|
||||
import android.service.credentials.CreateCredentialResponse;
|
||||
import android.service.credentials.CredentialProviderInfo;
|
||||
import android.service.credentials.CredentialProviderService;
|
||||
import android.service.credentials.SaveEntry;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Central provider session that listens for provider callbacks, and maintains provider state.
|
||||
* Will likely split this into remote response state and UI state.
|
||||
*/
|
||||
public final class ProviderCreateSession extends ProviderSession<
|
||||
CreateCredentialRequest, CreateCredentialResponse> {
|
||||
private static final String TAG = "ProviderCreateSession";
|
||||
|
||||
// Key to be used as an entry key for a save entry
|
||||
private static final String SAVE_ENTRY_KEY = "save_entry_key";
|
||||
|
||||
@NonNull
|
||||
private final Map<String, SaveEntry> mUiSaveEntries = new HashMap<>();
|
||||
/** The complete request to be used in the second round. */
|
||||
private final CreateCredentialRequest mCompleteRequest;
|
||||
|
||||
/** Creates a new provider session to be used by the request session. */
|
||||
@Nullable public static ProviderCreateSession createNewSession(
|
||||
Context context,
|
||||
@UserIdInt int userId,
|
||||
CredentialProviderInfo providerInfo,
|
||||
CreateRequestSession createRequestSession,
|
||||
RemoteCredentialService remoteCredentialService) {
|
||||
CreateCredentialRequest providerRequest =
|
||||
createProviderRequest(providerInfo.getCapabilities(),
|
||||
createRequestSession.mClientRequest,
|
||||
createRequestSession.mClientCallingPackage);
|
||||
if (providerRequest != null) {
|
||||
return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
|
||||
remoteCredentialService, providerRequest);
|
||||
}
|
||||
Log.i(TAG, "Unable to create provider session");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static CreateCredentialRequest createProviderRequest(List<String> providerCapabilities,
|
||||
android.credentials.CreateCredentialRequest clientRequest,
|
||||
String clientCallingPackage) {
|
||||
String capability = clientRequest.getType();
|
||||
if (providerCapabilities.contains(capability)) {
|
||||
return new CreateCredentialRequest(clientCallingPackage, capability,
|
||||
clientRequest.getData());
|
||||
}
|
||||
Log.i(TAG, "Unable to create provider request - capabilities do not match");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static CreateCredentialRequest getFirstRoundRequest(CreateCredentialRequest request) {
|
||||
// TODO: Replace with first round bundle from request when ready
|
||||
return new CreateCredentialRequest(
|
||||
request.getCallingPackage(),
|
||||
request.getType(),
|
||||
new Bundle());
|
||||
}
|
||||
|
||||
private ProviderCreateSession(
|
||||
@NonNull Context context,
|
||||
@NonNull CredentialProviderInfo info,
|
||||
@NonNull ProviderInternalCallback callbacks,
|
||||
@UserIdInt int userId,
|
||||
@NonNull RemoteCredentialService remoteCredentialService,
|
||||
@NonNull CreateCredentialRequest request) {
|
||||
super(context, info, getFirstRoundRequest(request), callbacks, userId,
|
||||
remoteCredentialService);
|
||||
// TODO : Replace with proper splitting of request
|
||||
mCompleteRequest = request;
|
||||
setStatus(Status.PENDING);
|
||||
}
|
||||
|
||||
/** Returns the save entry maintained in state by this provider session. */
|
||||
public SaveEntry getUiSaveEntry(String entryId) {
|
||||
return mUiSaveEntries.get(entryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderResponseSuccess(
|
||||
@Nullable CreateCredentialResponse response) {
|
||||
Log.i(TAG, "in onProviderResponseSuccess");
|
||||
onUpdateResponse(response);
|
||||
}
|
||||
|
||||
/** Called when the provider response resulted in a failure. */
|
||||
@Override
|
||||
public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
|
||||
updateStatusAndInvokeCallback(toStatus(errorCode));
|
||||
}
|
||||
|
||||
/** Called when provider service dies. */
|
||||
@Override
|
||||
public void onProviderServiceDied(RemoteCredentialService service) {
|
||||
if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
|
||||
updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
|
||||
} else {
|
||||
Slog.i(TAG, "Component names different in onProviderServiceDied - "
|
||||
+ "this should not happen");
|
||||
}
|
||||
}
|
||||
|
||||
private void onUpdateResponse(CreateCredentialResponse response) {
|
||||
Log.i(TAG, "updateResponse with save entries");
|
||||
mProviderResponse = response;
|
||||
updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable protected CreateCredentialProviderData prepareUiData()
|
||||
throws IllegalArgumentException {
|
||||
Log.i(TAG, "In prepareUiData");
|
||||
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
|
||||
Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
|
||||
return null;
|
||||
}
|
||||
final CreateCredentialResponse response = getProviderResponse();
|
||||
if (response == null) {
|
||||
Log.i(TAG, "In prepareUiData response null");
|
||||
throw new IllegalStateException("Response must be in completion mode");
|
||||
}
|
||||
if (response.getSaveEntries() != null) {
|
||||
Log.i(TAG, "In prepareUiData save entries not null");
|
||||
return prepareUiProviderData(
|
||||
prepareUiSaveEntries(response.getSaveEntries()),
|
||||
null,
|
||||
/*isDefaultProvider=*/false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderIntentResult(Bundle resultData) {
|
||||
Credential credential = resultData.getParcelable(
|
||||
CredentialProviderService.EXTRA_SAVE_CREDENTIAL,
|
||||
Credential.class);
|
||||
if (credential == null) {
|
||||
Log.i(TAG, "Credential returned from intent is null");
|
||||
return;
|
||||
}
|
||||
updateFinalCredentialResponse(credential);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUiEntrySelected(String entryType, String entryKey) {
|
||||
if (entryType.equals(SAVE_ENTRY_KEY)) {
|
||||
SaveEntry saveEntry = mUiSaveEntries.get(entryKey);
|
||||
if (saveEntry == null) {
|
||||
Log.i(TAG, "Save entry not found");
|
||||
return;
|
||||
}
|
||||
// TODO: Uncomment when pending intent works
|
||||
// onSaveEntrySelected(saveEntry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderIntentCancelled() {
|
||||
//TODO (Implement)
|
||||
}
|
||||
|
||||
private List<Entry> prepareUiSaveEntries(@NonNull List<SaveEntry> saveEntries) {
|
||||
Log.i(TAG, "in populateUiSaveEntries");
|
||||
List<Entry> uiSaveEntries = new ArrayList<>();
|
||||
|
||||
// Populate the save entries
|
||||
for (SaveEntry saveEntry : saveEntries) {
|
||||
String entryId = generateEntryId();
|
||||
mUiSaveEntries.put(entryId, saveEntry);
|
||||
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
|
||||
uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice()));
|
||||
}
|
||||
return uiSaveEntries;
|
||||
}
|
||||
|
||||
private void updateFinalCredentialResponse(@NonNull Credential credential) {
|
||||
mFinalCredentialResponse = credential;
|
||||
updateStatusAndInvokeCallback(Status.CREDENTIAL_RECEIVED_FROM_INTENT);
|
||||
}
|
||||
|
||||
private CreateCredentialProviderData prepareUiProviderData(List<Entry> saveEntries,
|
||||
Entry remoteEntry, boolean isDefaultProvider) {
|
||||
return new CreateCredentialProviderData.Builder(
|
||||
mComponentName.flattenToString())
|
||||
.setSaveEntries(saveEntries)
|
||||
.setIsDefaultProvider(isDefaultProvider)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void onSaveEntrySelected(SaveEntry saveEntry) {
|
||||
mProviderIntentController.setupAndInvokePendingIntent(saveEntry.getPendingIntent(),
|
||||
mProviderRequest);
|
||||
setStatus(Status.PENDING_INTENT_INVOKED);
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,16 @@ package com.android.server.credentials;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.slice.Slice;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
import android.credentials.GetCredentialOption;
|
||||
import android.credentials.ui.Entry;
|
||||
import android.credentials.ui.GetCredentialProviderData;
|
||||
import android.os.Bundle;
|
||||
import android.service.credentials.Action;
|
||||
import android.service.credentials.CredentialEntry;
|
||||
import android.service.credentials.CredentialProviderInfo;
|
||||
import android.service.credentials.CredentialsDisplayContent;
|
||||
import android.service.credentials.GetCredentialsRequest;
|
||||
import android.service.credentials.GetCredentialsResponse;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
@@ -38,75 +41,95 @@ import java.util.UUID;
|
||||
/**
|
||||
* Central provider session that listens for provider callbacks, and maintains provider state.
|
||||
* Will likely split this into remote response state and UI state.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class ProviderGetSession extends ProviderSession<GetCredentialsResponse>
|
||||
implements RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
|
||||
public final class ProviderGetSession extends ProviderSession<GetCredentialsRequest,
|
||||
GetCredentialsResponse>
|
||||
implements
|
||||
RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
|
||||
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";
|
||||
|
||||
private GetCredentialsResponse mResponse;
|
||||
|
||||
@NonNull
|
||||
private final Map<String, CredentialEntry> mUiCredentials = new HashMap<>();
|
||||
|
||||
private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
|
||||
@NonNull
|
||||
private final Map<String, Action> mUiActions = new HashMap<>();
|
||||
private final Map<String, Action> mUiActionsEntries = new HashMap<>();
|
||||
private Action mAuthenticationAction = null;
|
||||
|
||||
public ProviderGetSession(CredentialProviderInfo info,
|
||||
ProviderInternalCallback callbacks,
|
||||
int userId, RemoteCredentialService remoteCredentialService) {
|
||||
super(info, callbacks, userId, remoteCredentialService);
|
||||
setStatus(Status.PENDING);
|
||||
}
|
||||
|
||||
/** Updates the response being maintained in state by this provider session. */
|
||||
@Override
|
||||
public void updateResponse(GetCredentialsResponse response) {
|
||||
if (response.getAuthenticationAction() != null) {
|
||||
// TODO : Implement authentication logic
|
||||
} else if (response.getCredentialsDisplayContent() != null) {
|
||||
Log.i(TAG , "updateResponse with credentialEntries");
|
||||
mResponse = response;
|
||||
updateStatusAndInvokeCallback(Status.COMPLETE);
|
||||
/** Creates a new provider session to be used by the request session. */
|
||||
@Nullable public static ProviderGetSession createNewSession(
|
||||
Context context,
|
||||
@UserIdInt int userId,
|
||||
CredentialProviderInfo providerInfo,
|
||||
GetRequestSession getRequestSession,
|
||||
RemoteCredentialService remoteCredentialService) {
|
||||
GetCredentialsRequest providerRequest =
|
||||
createProviderRequest(providerInfo.getCapabilities(),
|
||||
getRequestSession.mClientRequest,
|
||||
getRequestSession.mClientCallingPackage);
|
||||
if (providerRequest != null) {
|
||||
return new ProviderGetSession(context, providerInfo, getRequestSession, userId,
|
||||
remoteCredentialService, providerRequest);
|
||||
}
|
||||
Log.i(TAG, "Unable to create provider session");
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns the response being maintained in this provider session. */
|
||||
@Override
|
||||
@Nullable
|
||||
public GetCredentialsResponse getResponse() {
|
||||
return mResponse;
|
||||
private static GetCredentialsRequest createProviderRequest(List<String> providerCapabilities,
|
||||
android.credentials.GetCredentialRequest clientRequest,
|
||||
String clientCallingPackage) {
|
||||
List<GetCredentialOption> filteredOptions = new ArrayList<>();
|
||||
for (GetCredentialOption option : clientRequest.getGetCredentialOptions()) {
|
||||
if (providerCapabilities.contains(option.getType())) {
|
||||
Log.i(TAG, "In createProviderRequest - capability found : " + option.getType());
|
||||
filteredOptions.add(option);
|
||||
} else {
|
||||
Log.i(TAG, "In createProviderRequest - capability not "
|
||||
+ "found : " + option.getType());
|
||||
}
|
||||
}
|
||||
if (!filteredOptions.isEmpty()) {
|
||||
return new GetCredentialsRequest.Builder(clientCallingPackage).setGetCredentialOptions(
|
||||
filteredOptions).build();
|
||||
}
|
||||
Log.i(TAG, "In createProviderRequest - returning null");
|
||||
return null;
|
||||
}
|
||||
|
||||
public ProviderGetSession(Context context,
|
||||
CredentialProviderInfo info,
|
||||
ProviderInternalCallback callbacks,
|
||||
int userId, RemoteCredentialService remoteCredentialService,
|
||||
GetCredentialsRequest request) {
|
||||
super(context, info, request, callbacks, userId, remoteCredentialService);
|
||||
setStatus(Status.PENDING);
|
||||
}
|
||||
|
||||
/** Returns the credential entry maintained in state by this provider session. */
|
||||
@Nullable
|
||||
public CredentialEntry getCredentialEntry(@NonNull String entryId) {
|
||||
return mUiCredentials.get(entryId);
|
||||
}
|
||||
|
||||
/** Returns the action entry maintained in state by this provider session. */
|
||||
@Nullable
|
||||
public Action getAction(@NonNull String entryId) {
|
||||
return mUiActions.get(entryId);
|
||||
return mUiCredentialEntries.get(entryId);
|
||||
}
|
||||
|
||||
/** Called when the provider response has been updated by an external source. */
|
||||
@Override
|
||||
@Override // Callback from the remote provider
|
||||
public void onProviderResponseSuccess(@Nullable GetCredentialsResponse response) {
|
||||
Log.i(TAG, "in onProviderResponseSuccess");
|
||||
updateResponse(response);
|
||||
onUpdateResponse(response);
|
||||
}
|
||||
|
||||
/** Called when the provider response resulted in a failure. */
|
||||
@Override
|
||||
@Override // Callback from the remote provider
|
||||
public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
|
||||
updateStatusAndInvokeCallback(toStatus(errorCode));
|
||||
}
|
||||
|
||||
/** Called when provider service dies. */
|
||||
@Override
|
||||
@Override // Callback from the remote provider
|
||||
public void onProviderServiceDied(RemoteCredentialService service) {
|
||||
if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
|
||||
updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
|
||||
@@ -116,77 +139,106 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsResp
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
|
||||
Log.i(TAG, "In prepareUiData");
|
||||
if (!ProviderSession.isCompletionStatus(getStatus())) {
|
||||
Log.i(TAG, "In prepareUiData not complete");
|
||||
@Override // Callback from the provider intent controller class
|
||||
public void onProviderIntentResult(Bundle resultData) {
|
||||
// TODO : Implement
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Status must be in completion mode");
|
||||
@Override
|
||||
public void onProviderIntentCancelled() {
|
||||
// TODO : Implement
|
||||
}
|
||||
|
||||
@Override // Selection call from the request provider
|
||||
protected void onUiEntrySelected(String entryType, String entryId) {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
@Override // Call from request session to data to be shown on the UI
|
||||
@Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
|
||||
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;
|
||||
}
|
||||
GetCredentialsResponse response = getResponse();
|
||||
GetCredentialsResponse response = getProviderResponse();
|
||||
if (response == null) {
|
||||
Log.i(TAG, "In prepareUiData response null");
|
||||
|
||||
throw new IllegalStateException("Response must be in completion mode");
|
||||
}
|
||||
if (response.getAuthenticationAction() != null) {
|
||||
Log.i(TAG, "In prepareUiData auth not null");
|
||||
|
||||
return prepareUiProviderDataWithAuthentication(response.getAuthenticationAction());
|
||||
Log.i(TAG, "In prepareUiData - top level authentication mode");
|
||||
return prepareUiProviderData(null, null,
|
||||
prepareUiAuthenticationActionEntry(response.getAuthenticationAction()),
|
||||
/*remoteEntry=*/null);
|
||||
}
|
||||
if (response.getCredentialsDisplayContent() != null){
|
||||
Log.i(TAG, "In prepareUiData credentials not null");
|
||||
|
||||
return prepareUiProviderDataWithCredentials(response.getCredentialsDisplayContent());
|
||||
Log.i(TAG, "In prepareUiData displayContent not null");
|
||||
return prepareUiProviderData(populateUiActionEntries(
|
||||
response.getCredentialsDisplayContent().getActions()),
|
||||
prepareUiCredentialEntries(response.getCredentialsDisplayContent()
|
||||
.getCredentialEntries()),
|
||||
/*authenticationActionEntry=*/null, /*remoteEntry=*/null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called by {@link ProviderGetSession} when the UI is to be invoked.
|
||||
*/
|
||||
@Nullable
|
||||
private GetCredentialProviderData prepareUiProviderDataWithCredentials(@NonNull
|
||||
CredentialsDisplayContent content) {
|
||||
Log.i(TAG, "in prepareUiProviderData");
|
||||
List<Entry> credentialEntries = new ArrayList<>();
|
||||
List<Entry> actionChips = new ArrayList<>();
|
||||
Entry authenticationEntry = null;
|
||||
private Entry prepareUiAuthenticationActionEntry(@NonNull Action authenticationAction) {
|
||||
String entryId = generateEntryId();
|
||||
mUiActionsEntries.put(entryId, authenticationAction);
|
||||
return new Entry(ACTION_ENTRY_KEY, entryId, authenticationAction.getSlice());
|
||||
}
|
||||
|
||||
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 : content.getCredentialEntries()) {
|
||||
String entryId = UUID.randomUUID().toString();
|
||||
mUiCredentials.put(entryId, credentialEntry);
|
||||
for (CredentialEntry credentialEntry : credentialEntries) {
|
||||
String entryId = generateEntryId();
|
||||
mUiCredentialEntries.put(entryId, credentialEntry);
|
||||
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
|
||||
Slice slice = credentialEntry.getSlice();
|
||||
// TODO : Remove conversion of string to int after change in Entry class
|
||||
credentialEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
|
||||
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
|
||||
credentialEntry.getSlice()));
|
||||
}
|
||||
// populate the action chip
|
||||
for (Action action : content.getActions()) {
|
||||
String entryId = UUID.randomUUID().toString();
|
||||
mUiActions.put(entryId, action);
|
||||
// TODO : Remove conversion of string to int after change in Entry class
|
||||
actionChips.add(new Entry(ACTION_ENTRY_KEY, entryId,
|
||||
action.getSlice()));
|
||||
}
|
||||
return credentialUiEntries;
|
||||
}
|
||||
|
||||
return new GetCredentialProviderData.Builder(mComponentName.flattenToString())
|
||||
private List<Entry> populateUiActionEntries(@Nullable List<Action> actions) {
|
||||
List<Entry> actionEntries = new ArrayList<>();
|
||||
for (Action action : actions) {
|
||||
String entryId = UUID.randomUUID().toString();
|
||||
mUiActionsEntries.put(entryId, action);
|
||||
// TODO : Remove conversion of string to int after change in Entry class
|
||||
actionEntries.add(new Entry(ACTION_ENTRY_KEY, entryId, action.getSlice()));
|
||||
}
|
||||
return actionEntries;
|
||||
}
|
||||
|
||||
private GetCredentialProviderData prepareUiProviderData(List<Entry> actionEntries,
|
||||
List<Entry> credentialEntries, Entry authenticationActionEntry,
|
||||
Entry remoteEntry) {
|
||||
return new GetCredentialProviderData.Builder(
|
||||
mComponentName.flattenToString()).setActionChips(actionEntries)
|
||||
.setCredentialEntries(credentialEntries)
|
||||
.setActionChips(actionChips)
|
||||
.setAuthenticationEntry(authenticationEntry)
|
||||
.setAuthenticationEntry(authenticationActionEntry)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called by {@link ProviderGetSession} when the UI is to be invoked.
|
||||
*/
|
||||
@Nullable
|
||||
private GetCredentialProviderData prepareUiProviderDataWithAuthentication(@NonNull
|
||||
Action authenticationEntry) {
|
||||
// TODO : Implement authentication flow
|
||||
return null;
|
||||
/** Updates the response being maintained in state by this provider session. */
|
||||
private void onUpdateResponse(GetCredentialsResponse response) {
|
||||
mProviderResponse = response;
|
||||
if (response.getAuthenticationAction() != null) {
|
||||
Log.i(TAG , "updateResponse with authentication entry");
|
||||
// TODO validate authentication action
|
||||
mAuthenticationAction = response.getAuthenticationAction();
|
||||
updateStatusAndInvokeCallback(Status.REQUIRES_AUTHENTICATION);
|
||||
} else if (response.getCredentialsDisplayContent() != null) {
|
||||
Log.i(TAG , "updateResponse with credentialEntries");
|
||||
// TODO validate response
|
||||
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.server.credentials;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcel;
|
||||
import android.os.ResultReceiver;
|
||||
import android.service.credentials.CreateCredentialRequest;
|
||||
import android.service.credentials.CredentialProviderService;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Class that invokes providers' pending intents and listens to the responses.
|
||||
*/
|
||||
@SuppressLint("LongLogTag")
|
||||
public class ProviderIntentController {
|
||||
private static final String TAG = "ProviderIntentController";
|
||||
/**
|
||||
* Interface to be implemented by any class that wishes to get callbacks from the UI.
|
||||
*/
|
||||
public interface ProviderIntentControllerCallback {
|
||||
/** Called when the user makes a selection. */
|
||||
void onProviderIntentResult(Bundle resultData);
|
||||
/** Called when the user cancels the UI. */
|
||||
void onProviderIntentCancelled();
|
||||
}
|
||||
|
||||
private final int mUserId;
|
||||
private final Context mContext;
|
||||
private final ProviderIntentControllerCallback mCallback;
|
||||
private final ResultReceiver mResultReceiver = new ResultReceiver(
|
||||
new Handler(Looper.getMainLooper())) {
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
Log.i(TAG, "onReceiveResult in providerIntentController");
|
||||
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Log.i(TAG, "onReceiveResult - ACTIVITYOK");
|
||||
mCallback.onProviderIntentResult(resultData);
|
||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
Log.i(TAG, "onReceiveResult - RESULTCANCELED");
|
||||
mCallback.onProviderIntentCancelled();
|
||||
}
|
||||
// Drop unknown result
|
||||
}
|
||||
};
|
||||
|
||||
public ProviderIntentController(@UserIdInt int userId,
|
||||
Context context,
|
||||
ProviderIntentControllerCallback callback) {
|
||||
mUserId = userId;
|
||||
mContext = context;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
/** Sets up the request data and invokes the given pending intent. */
|
||||
public void setupAndInvokePendingIntent(@NonNull PendingIntent pendingIntent,
|
||||
CreateCredentialRequest request) {
|
||||
Log.i(TAG, "in invokePendingIntent");
|
||||
setupIntent(pendingIntent, request);
|
||||
Log.i(TAG, "in invokePendingIntent receiver set up");
|
||||
Log.i(TAG, "creator package: " + pendingIntent.getIntentSender()
|
||||
.getCreatorPackage());
|
||||
|
||||
try {
|
||||
mContext.startIntentSender(pendingIntent.getIntentSender(),
|
||||
null, 0, 0, 0);
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
Log.i(TAG, "Error while invoking pending intent");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setupIntent(PendingIntent pendingIntent, CreateCredentialRequest request) {
|
||||
pendingIntent.getIntent().putExtra(Intent.EXTRA_RESULT_RECEIVER,
|
||||
toIpcFriendlyResultReceiver(mResultReceiver));
|
||||
pendingIntent.getIntent().putExtra(
|
||||
CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
|
||||
request.getData());
|
||||
}
|
||||
|
||||
private <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver(
|
||||
T resultReceiver) {
|
||||
final Parcel parcel = Parcel.obtain();
|
||||
resultReceiver.writeToParcel(parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
|
||||
final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
|
||||
parcel.recycle();
|
||||
|
||||
return ipcFriendly;
|
||||
}
|
||||
}
|
||||
@@ -17,25 +17,70 @@
|
||||
package com.android.server.credentials;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.credentials.Credential;
|
||||
import android.credentials.ui.ProviderData;
|
||||
import android.os.Bundle;
|
||||
import android.service.credentials.CredentialProviderException;
|
||||
import android.service.credentials.CredentialProviderInfo;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Provider session storing the state of provider response and ui entries.
|
||||
* @param <T> The request type expected from the remote provider, for a given request session.
|
||||
* @param <T> The request to be sent to the provider
|
||||
* @param <R> The response to be expected from the provider
|
||||
*/
|
||||
public abstract class ProviderSession<T> implements RemoteCredentialService.ProviderCallbacks<T> {
|
||||
public abstract class ProviderSession<T, R> implements RemoteCredentialService.ProviderCallbacks<R>,
|
||||
ProviderIntentController.ProviderIntentControllerCallback {
|
||||
// Key to be used as the entry key for an action entry
|
||||
protected static final String ACTION_ENTRY_KEY = "action_key";
|
||||
|
||||
@NonNull protected final Context mContext;
|
||||
@NonNull protected final ComponentName mComponentName;
|
||||
@NonNull protected final CredentialProviderInfo mProviderInfo;
|
||||
@NonNull protected final RemoteCredentialService mRemoteCredentialService;
|
||||
@NonNull protected final int mUserId;
|
||||
@NonNull protected Status mStatus = Status.NOT_STARTED;
|
||||
@NonNull protected final ProviderInternalCallback mCallbacks;
|
||||
@NonNull protected final ProviderIntentController mProviderIntentController;
|
||||
@Nullable protected Credential mFinalCredentialResponse;
|
||||
@NonNull protected final T mProviderRequest;
|
||||
@Nullable protected R mProviderResponse;
|
||||
|
||||
/**
|
||||
* Returns true if the given status reflects that the provider state is ready to be shown
|
||||
* on the credMan UI.
|
||||
*/
|
||||
public static boolean isUiInvokingStatus(Status status) {
|
||||
return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given status reflects that the provider is waiting for a remote
|
||||
* response.
|
||||
*/
|
||||
public static boolean isStatusWaitingForRemoteResponse(Status status) {
|
||||
return status == Status.PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given status means that the provider session must be terminated.
|
||||
*/
|
||||
public static boolean isTerminatingStatus(Status status) {
|
||||
return status == Status.CANCELED || status == Status.SERVICE_DEAD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given status reflects that the provider is done getting the response,
|
||||
* and is ready to return the final credential back to the user.
|
||||
*/
|
||||
public static boolean isCompletionStatus(Status status) {
|
||||
return status == Status.CREDENTIAL_RECEIVED_FROM_INTENT
|
||||
|| status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to be implemented by any class that wishes to get a callback when a particular
|
||||
@@ -49,35 +94,49 @@ public abstract class ProviderSession<T> implements RemoteCredentialService.Prov
|
||||
void onProviderStatusChanged(Status status, ComponentName componentName);
|
||||
}
|
||||
|
||||
protected ProviderSession(@NonNull CredentialProviderInfo info,
|
||||
protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
|
||||
@NonNull T providerRequest,
|
||||
@NonNull ProviderInternalCallback callbacks,
|
||||
@NonNull int userId,
|
||||
@NonNull RemoteCredentialService remoteCredentialService) {
|
||||
mContext = context;
|
||||
mProviderInfo = info;
|
||||
mProviderRequest = providerRequest;
|
||||
mCallbacks = callbacks;
|
||||
mUserId = userId;
|
||||
mComponentName = info.getServiceInfo().getComponentName();
|
||||
mRemoteCredentialService = remoteCredentialService;
|
||||
mProviderIntentController = new ProviderIntentController(userId, context, this);
|
||||
}
|
||||
|
||||
/** Update the response state stored with the provider session. */
|
||||
protected abstract void updateResponse (T response);
|
||||
|
||||
/** Update the response state stored with the provider session. */
|
||||
protected abstract T getResponse ();
|
||||
|
||||
/** Should be overridden to prepare, and stores state for {@link ProviderData} to be
|
||||
* shown on the UI. */
|
||||
protected abstract ProviderData prepareUiData();
|
||||
|
||||
/** Provider status at various states of the request session. */
|
||||
// TODO: Review status values, and adjust where needed
|
||||
enum Status {
|
||||
NOT_STARTED,
|
||||
PENDING,
|
||||
REQUIRES_AUTHENTICATION,
|
||||
COMPLETE,
|
||||
CREDENTIALS_RECEIVED,
|
||||
SERVICE_DEAD,
|
||||
CANCELED
|
||||
CREDENTIAL_RECEIVED_FROM_INTENT,
|
||||
PENDING_INTENT_INVOKED,
|
||||
CREDENTIAL_RECEIVED_FROM_SELECTION,
|
||||
SAVE_ENTRIES_RECEIVED, CANCELED
|
||||
}
|
||||
|
||||
/** Converts exception to a provider session status. */
|
||||
@NonNull
|
||||
public static Status toStatus(
|
||||
@CredentialProviderException.CredentialProviderError int errorCode) {
|
||||
// TODO : Add more mappings as more flows are supported
|
||||
return Status.CANCELED;
|
||||
}
|
||||
|
||||
protected String generateEntryId() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public Credential getFinalCredentialResponse() {
|
||||
return mFinalCredentialResponse;
|
||||
}
|
||||
|
||||
protected void setStatus(@NonNull Status status) {
|
||||
@@ -94,31 +153,38 @@ public abstract class ProviderSession<T> implements RemoteCredentialService.Prov
|
||||
return mComponentName;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected RemoteCredentialService getRemoteCredentialService() {
|
||||
return mRemoteCredentialService;
|
||||
}
|
||||
|
||||
/** Updates the status .*/
|
||||
protected void updateStatusAndInvokeCallback(@NonNull Status status) {
|
||||
setStatus(status);
|
||||
mCallbacks.onProviderStatusChanged(status, mComponentName);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Status toStatus(
|
||||
@CredentialProviderException.CredentialProviderError int errorCode) {
|
||||
// TODO : Add more mappings as more flows are supported
|
||||
return Status.CANCELED;
|
||||
/** Get the request to be sent to the provider. */
|
||||
protected T getProviderRequest() {
|
||||
return mProviderRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given status means that the provider session must be terminated.
|
||||
*/
|
||||
public static boolean isTerminatingStatus(Status status) {
|
||||
return status == Status.CANCELED || status == Status.SERVICE_DEAD;
|
||||
/** Update the response state stored with the provider session. */
|
||||
@Nullable protected R getProviderResponse() {
|
||||
return mProviderResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given status means that the provider is done getting the response,
|
||||
* and is ready for user interaction.
|
||||
*/
|
||||
public static boolean isCompletionStatus(Status status) {
|
||||
return status == Status.COMPLETE || status == Status.REQUIRES_AUTHENTICATION;
|
||||
}
|
||||
/** Should be overridden to prepare, and stores state for {@link ProviderData} to be
|
||||
* shown on the UI. */
|
||||
@Nullable protected abstract ProviderData prepareUiData();
|
||||
|
||||
/** Should be overridden to handle the selected entry from the UI. */
|
||||
protected abstract void onUiEntrySelected(String entryType, String entryId);
|
||||
|
||||
@Override
|
||||
public abstract void onProviderIntentResult(Bundle resultData);
|
||||
|
||||
@Override
|
||||
public abstract void onProviderIntentCancelled();
|
||||
|
||||
}
|
||||
|
||||
@@ -24,11 +24,14 @@ import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.RemoteException;
|
||||
import android.service.credentials.CreateCredentialRequest;
|
||||
import android.service.credentials.CreateCredentialResponse;
|
||||
import android.service.credentials.CredentialProviderException;
|
||||
import android.service.credentials.CredentialProviderException.CredentialProviderError;
|
||||
import android.service.credentials.CredentialProviderService;
|
||||
import android.service.credentials.GetCredentialsRequest;
|
||||
import android.service.credentials.GetCredentialsResponse;
|
||||
import android.service.credentials.ICreateCredentialCallback;
|
||||
import android.service.credentials.ICredentialProviderService;
|
||||
import android.service.credentials.IGetCredentialsCallback;
|
||||
import android.text.format.DateUtils;
|
||||
@@ -76,7 +79,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
|
||||
public RemoteCredentialService(@NonNull Context context,
|
||||
@NonNull ComponentName componentName, int userId) {
|
||||
super(context, new Intent(CredentialProviderService.SERVICE_INTERFACE)
|
||||
.setComponent(componentName), Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
|
||||
.setComponent(componentName), /*bindingFlags=*/0,
|
||||
userId, ICredentialProviderService.Stub::asInterface);
|
||||
mComponentName = componentName;
|
||||
}
|
||||
@@ -101,7 +104,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
|
||||
* provider service.
|
||||
* @param request the request to be sent to the provider
|
||||
* @param callback the callback to be used to send back the provider response to the
|
||||
* {@link ProviderSession} class that maintains provider state
|
||||
* {@link ProviderGetSession} class that maintains provider state
|
||||
*/
|
||||
public void onGetCredentials(@NonNull GetCredentialsRequest request,
|
||||
ProviderCallbacks<GetCredentialsResponse> callback) {
|
||||
@@ -114,21 +117,21 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
|
||||
CompletableFuture<GetCredentialsResponse> getCredentials = new CompletableFuture<>();
|
||||
ICancellationSignal cancellationSignal =
|
||||
service.onGetCredentials(request, new IGetCredentialsCallback.Stub() {
|
||||
@Override
|
||||
public void onSuccess(GetCredentialsResponse response) {
|
||||
Log.i(TAG, "In onSuccess in RemoteCredentialService");
|
||||
getCredentials.complete(response);
|
||||
}
|
||||
@Override
|
||||
public void onSuccess(GetCredentialsResponse response) {
|
||||
Log.i(TAG, "In onSuccess in RemoteCredentialService");
|
||||
getCredentials.complete(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@CredentialProviderError int errorCode,
|
||||
CharSequence message) {
|
||||
Log.i(TAG, "In onFailure in RemoteCredentialService");
|
||||
String errorMsg = message == null ? "" : String.valueOf(message);
|
||||
getCredentials.completeExceptionally(new CredentialProviderException(
|
||||
errorCode, errorMsg));
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onFailure(@CredentialProviderError int errorCode,
|
||||
CharSequence message) {
|
||||
Log.i(TAG, "In onFailure in RemoteCredentialService");
|
||||
String errorMsg = message == null ? "" : String.valueOf(message);
|
||||
getCredentials.completeExceptionally(new CredentialProviderException(
|
||||
errorCode, errorMsg));
|
||||
}
|
||||
});
|
||||
CompletableFuture<GetCredentialsResponse> future = futureRef.get();
|
||||
if (future != null && future.isCancelled()) {
|
||||
dispatchCancellationSignal(cancellationSignal);
|
||||
@@ -137,38 +140,91 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
|
||||
}
|
||||
return getCredentials;
|
||||
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
|
||||
futureRef.set(connectThenExecute);
|
||||
|
||||
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> {
|
||||
if (error == null) {
|
||||
Log.i(TAG, "In RemoteCredentialService execute error is null");
|
||||
callback.onProviderResponseSuccess(result);
|
||||
futureRef.set(connectThenExecute);
|
||||
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
|
||||
handleExecutionResponse(result, error, cancellationSink, callback)));
|
||||
}
|
||||
|
||||
/** Main entry point to be called for executing a createCredential call on the remote
|
||||
* provider service.
|
||||
* @param request the request to be sent to the provider
|
||||
* @param callback the callback to be used to send back the provider response to the
|
||||
* {@link ProviderCreateSession} class that maintains provider state
|
||||
*/
|
||||
public void onCreateCredential(@NonNull CreateCredentialRequest request,
|
||||
ProviderCallbacks<CreateCredentialResponse> callback) {
|
||||
Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
|
||||
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
|
||||
AtomicReference<CompletableFuture<CreateCredentialResponse>> futureRef =
|
||||
new AtomicReference<>();
|
||||
|
||||
CompletableFuture<CreateCredentialResponse> connectThenExecute = postAsync(service -> {
|
||||
CompletableFuture<CreateCredentialResponse> createCredentialFuture =
|
||||
new CompletableFuture<>();
|
||||
ICancellationSignal cancellationSignal = service.onCreateCredential(
|
||||
request, new ICreateCredentialCallback.Stub() {
|
||||
@Override
|
||||
public void onSuccess(CreateCredentialResponse response) {
|
||||
Log.i(TAG, "In onSuccess onCreateCredential "
|
||||
+ "in RemoteCredentialService");
|
||||
createCredentialFuture.complete(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@CredentialProviderError int errorCode,
|
||||
CharSequence message) {
|
||||
Log.i(TAG, "In onFailure in RemoteCredentialService");
|
||||
String errorMsg = message == null ? "" : String.valueOf(message);
|
||||
createCredentialFuture.completeExceptionally(
|
||||
new CredentialProviderException(errorCode, errorMsg));
|
||||
}});
|
||||
CompletableFuture<CreateCredentialResponse> future = futureRef.get();
|
||||
if (future != null && future.isCancelled()) {
|
||||
dispatchCancellationSignal(cancellationSignal);
|
||||
} else {
|
||||
if (error instanceof TimeoutException) {
|
||||
Log.i(TAG, "In RemoteCredentialService execute error is timeout");
|
||||
dispatchCancellationSignal(cancellationSink.get());
|
||||
callback.onProviderResponseFailure(
|
||||
CredentialProviderException.ERROR_TIMEOUT,
|
||||
error.getMessage());
|
||||
} else if (error instanceof CancellationException) {
|
||||
Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
|
||||
dispatchCancellationSignal(cancellationSink.get());
|
||||
callback.onProviderResponseFailure(
|
||||
CredentialProviderException.ERROR_TASK_CANCELED,
|
||||
error.getMessage());
|
||||
} else if (error instanceof CredentialProviderException) {
|
||||
Log.i(TAG, "In RemoteCredentialService execute error is provider error");
|
||||
callback.onProviderResponseFailure(((CredentialProviderException) error)
|
||||
.getErrorCode(),
|
||||
error.getMessage());
|
||||
} else {
|
||||
Log.i(TAG, "In RemoteCredentialService execute error is unknown");
|
||||
callback.onProviderResponseFailure(
|
||||
CredentialProviderException.ERROR_UNKNOWN,
|
||||
error.getMessage());
|
||||
}
|
||||
cancellationSink.set(cancellationSignal);
|
||||
}
|
||||
}));
|
||||
return createCredentialFuture;
|
||||
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
|
||||
|
||||
futureRef.set(connectThenExecute);
|
||||
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
|
||||
handleExecutionResponse(result, error, cancellationSink, callback)));
|
||||
}
|
||||
|
||||
private <T> void handleExecutionResponse(T result,
|
||||
Throwable error,
|
||||
AtomicReference<ICancellationSignal> cancellationSink,
|
||||
ProviderCallbacks<T> callback) {
|
||||
if (error == null) {
|
||||
Log.i(TAG, "In RemoteCredentialService execute error is null");
|
||||
callback.onProviderResponseSuccess(result);
|
||||
} else {
|
||||
if (error instanceof TimeoutException) {
|
||||
Log.i(TAG, "In RemoteCredentialService execute error is timeout");
|
||||
dispatchCancellationSignal(cancellationSink.get());
|
||||
callback.onProviderResponseFailure(
|
||||
CredentialProviderException.ERROR_TIMEOUT,
|
||||
error.getMessage());
|
||||
} else if (error instanceof CancellationException) {
|
||||
Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
|
||||
dispatchCancellationSignal(cancellationSink.get());
|
||||
callback.onProviderResponseFailure(
|
||||
CredentialProviderException.ERROR_TASK_CANCELED,
|
||||
error.getMessage());
|
||||
} else if (error instanceof CredentialProviderException) {
|
||||
Log.i(TAG, "In RemoteCredentialService execute error is provider error");
|
||||
callback.onProviderResponseFailure(((CredentialProviderException) error)
|
||||
.getErrorCode(),
|
||||
error.getMessage());
|
||||
} else {
|
||||
Log.i(TAG, "In RemoteCredentialService execute error is unknown");
|
||||
callback.onProviderResponseFailure(
|
||||
CredentialProviderException.ERROR_UNKNOWN,
|
||||
error.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
|
||||
|
||||
@@ -20,18 +20,30 @@ 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;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.service.credentials.CredentialProviderInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base class of a request session, that listens to UI events. This class must be extended
|
||||
* every time a new response type is expected from the providers.
|
||||
*/
|
||||
abstract class RequestSession implements CredentialManagerUi.CredentialManagerUiCallback,
|
||||
abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback,
|
||||
ProviderSession.ProviderInternalCallback {
|
||||
private static final String TAG = "RequestSession";
|
||||
|
||||
// TODO: Revise access levels of attributes
|
||||
@NonNull protected final T mClientRequest;
|
||||
@NonNull protected final U mClientCallback;
|
||||
@NonNull protected final IBinder mRequestId;
|
||||
@NonNull protected final Context mContext;
|
||||
@NonNull protected final CredentialManagerUi mCredentialManagerUi;
|
||||
@@ -39,29 +51,120 @@ abstract class RequestSession implements CredentialManagerUi.CredentialManagerUi
|
||||
@NonNull protected final Handler mHandler;
|
||||
@NonNull protected boolean mIsFirstUiTurn = true;
|
||||
@UserIdInt protected final int mUserId;
|
||||
@NonNull protected final String mClientCallingPackage;
|
||||
|
||||
protected final Map<String, ProviderSession> mProviders = new HashMap<>();
|
||||
|
||||
protected RequestSession(@NonNull Context context,
|
||||
@UserIdInt int userId, @NonNull String requestType) {
|
||||
@UserIdInt int userId, @NonNull T clientRequest, U clientCallback,
|
||||
@NonNull String requestType,
|
||||
String clientCallingPackage) {
|
||||
mContext = context;
|
||||
mUserId = userId;
|
||||
mClientRequest = clientRequest;
|
||||
mClientCallback = clientCallback;
|
||||
mRequestType = requestType;
|
||||
mClientCallingPackage = clientCallingPackage;
|
||||
mHandler = new Handler(Looper.getMainLooper(), null, true);
|
||||
mRequestId = new Binder();
|
||||
mCredentialManagerUi = new CredentialManagerUi(mContext,
|
||||
mUserId, this);
|
||||
}
|
||||
|
||||
/** Returns the unique identifier of this request session. */
|
||||
public IBinder getRequestId() {
|
||||
return mRequestId;
|
||||
public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
|
||||
RemoteCredentialService remoteCredentialService);
|
||||
|
||||
protected abstract void launchUiWithProviderData(ArrayList<ProviderData> providerDataList);
|
||||
|
||||
// UI callbacks
|
||||
|
||||
@Override // from CredentialManagerUiCallbacks
|
||||
public void onUiSelection(UserSelectionDialogResult selection) {
|
||||
String providerId = selection.getProviderId();
|
||||
Log.i(TAG, "onUiSelection, providerId: " + providerId);
|
||||
ProviderSession providerSession = mProviders.get(providerId);
|
||||
if (providerSession == null) {
|
||||
Log.i(TAG, "providerSession not found in onUiSelection");
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "Provider session found");
|
||||
providerSession.onUiEntrySelected(selection.getEntryKey(),
|
||||
selection.getEntrySubkey());
|
||||
}
|
||||
|
||||
@Override // from CredentialManagerUiCallback
|
||||
public abstract void onUiSelection(UserSelectionDialogResult selection);
|
||||
@Override // from CredentialManagerUiCallbacks
|
||||
public void onUiCancellation() {
|
||||
// User canceled the activity
|
||||
finishSession();
|
||||
}
|
||||
|
||||
@Override // from CredentialManagerUiCallback
|
||||
public abstract void onUiCancelation();
|
||||
@Override // from provider session
|
||||
public 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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from ProviderInternalCallback
|
||||
public abstract void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName);
|
||||
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() {
|
||||
clearProviderSessions();
|
||||
}
|
||||
|
||||
protected void clearProviderSessions() {
|
||||
//TODO: Implement
|
||||
mProviders.clear();
|
||||
}
|
||||
|
||||
private boolean isAnyProviderPending() {
|
||||
for (ProviderSession session : mProviders.values()) {
|
||||
if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void getProviderDataAndInitiateUi() {
|
||||
ArrayList<ProviderData> providerDataList = new ArrayList<>();
|
||||
for (ProviderSession session : mProviders.values()) {
|
||||
Log.i(TAG, "preparing data for : " + session.getComponentName());
|
||||
ProviderData providerData = session.prepareUiData();
|
||||
if (providerData != null) {
|
||||
Log.i(TAG, "Provider data is not null");
|
||||
providerDataList.add(providerData);
|
||||
}
|
||||
}
|
||||
if (!providerDataList.isEmpty()) {
|
||||
Log.i(TAG, "provider list not empty about to initiate ui");
|
||||
launchUiWithProviderData(providerDataList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user