Merge "Add clear cred implementaion Test: Built locally"
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index c3937b6..5eaf2a0 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -44,6 +44,8 @@
public static final @NonNull String EXTRA_REQUEST_INFO =
"android.credentials.ui.extra.REQUEST_INFO";
+ /** Type value for any request that does not require UI. */
+ public static final @NonNull String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
/** Type value for an executeGetCredential request. */
public static final @NonNull String TYPE_GET = "android.credentials.ui.TYPE_GET";
/** Type value for an executeCreateCredential request. */
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
new file mode 100644
index 0000000..6b254bf
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -0,0 +1,142 @@
+/*
+ * 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.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.ClearCredentialStateRequest;
+import android.credentials.IClearCredentialStateCallback;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.os.RemoteException;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Central session for a single clearCredentialState request. This class listens to the
+ * responses from providers, and updates the provider(S) state.
+ */
+public final class ClearRequestSession extends RequestSession<ClearCredentialStateRequest,
+ IClearCredentialStateCallback>
+ implements ProviderSession.ProviderInternalCallback<Void> {
+ private static final String TAG = "GetRequestSession";
+
+ public ClearRequestSession(Context context, int userId,
+ IClearCredentialStateCallback callback, ClearCredentialStateRequest request,
+ String callingPackage) {
+ super(context, userId, request, callback, RequestInfo.TYPE_UNDEFINED, callingPackage);
+ }
+
+ /**
+ * 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.
+ */
+ @Override
+ @Nullable
+ public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+ RemoteCredentialService remoteCredentialService) {
+ ProviderClearSession providerClearSession = ProviderClearSession
+ .createNewSession(mContext, mUserId, providerInfo,
+ this, remoteCredentialService);
+ if (providerClearSession != null) {
+ Log.i(TAG, "In startProviderSession - provider session created and being added");
+ mProviders.put(providerClearSession.getComponentName().flattenToString(),
+ providerClearSession);
+ }
+ return providerClearSession;
+ }
+
+ @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);
+ } else if (ProviderSession.isCompletionStatus(status)) {
+ Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+ onProviderResponseComplete(componentName);
+ }
+ }
+
+ @Override
+ public void onFinalResponseReceived(ComponentName componentName, Void response) {
+ respondToClientWithResponseAndFinish();
+ }
+
+ @Override
+ protected void onProviderResponseComplete(ComponentName componentName) {
+ if (!isAnyProviderPending()) {
+ onFinalResponseReceived(componentName, null);
+ }
+ }
+
+ @Override
+ protected void onProviderTerminated(ComponentName componentName) {
+ if (!isAnyProviderPending()) {
+ processResponses();
+ }
+ }
+
+ @Override
+ protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+ //Not applicable for clearCredential as UI is not needed
+ }
+
+ @Override
+ public void onFinalErrorReceived(ComponentName componentName, String errorType,
+ String message) {
+ //Not applicable for clearCredential as response is not picked by the user
+ }
+
+ private void respondToClientWithResponseAndFinish() {
+ Log.i(TAG, "respondToClientWithResponseAndFinish");
+ try {
+ mClientCallback.onSuccess();
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue while propagating the response to the client");
+ }
+ finishSession();
+ }
+
+ private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+ Log.i(TAG, "respondToClientWithErrorAndFinish");
+ try {
+ mClientCallback.onError(errorType, errorMsg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ finishSession();
+ }
+ private void processResponses() {
+ for (ProviderSession session: mProviders.values()) {
+ if (session.isProviderResponseSet()) {
+ // If even one provider responded successfully, send back the response
+ // TODO: Aggregate other exceptions
+ respondToClientWithResponseAndFinish();
+ return;
+ }
+ }
+ // TODO: Replace with properly defined error type
+ respondToClientWithErrorAndFinish("unknown", "All providers failed");
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 1f74e93..d29f86e 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -314,9 +314,45 @@
@Override
public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
IClearCredentialStateCallback callback, String callingPackage) {
- // TODO: implement.
- Log.i(TAG, "clearCredentialSession");
+ Log.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage);
+ // TODO : Implement cancellation
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+ // New request session, scoped for this request only.
+ final ClearRequestSession session =
+ new ClearRequestSession(
+ getContext(),
+ UserHandle.getCallingUserId(),
+ callback,
+ request,
+ callingPackage);
+
+ // Initiate all provider sessions
+ // TODO: Determine if provider needs to have clear capability in their manifest
+ List<ProviderSession> providerSessions =
+ initiateProviderSessions(session, List.of());
+
+ if (providerSessions.isEmpty()) {
+ try {
+ // TODO("Replace with properly defined error type")
+ callback.onError("unknown_type",
+ "No providers available to fulfill request.");
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue invoking onError on IClearCredentialStateCallback "
+ + "callback: " + e.getMessage());
+ }
+ }
+
+ // Iterate over all provider sessions and invoke the request
+ providerSessions.forEach(
+ providerClearSession -> {
+ providerClearSession
+ .getRemoteCredentialService()
+ .onClearCredentialState(
+ (android.service.credentials.ClearCredentialStateRequest)
+ providerClearSession.getProviderRequest(),
+ /* callback= */ providerClearSession);
+ });
return cancelTransport;
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 08fdeed..c03d505 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -76,7 +76,7 @@
@GuardedBy("mLock")
public ProviderSession initiateProviderSessionForRequestLocked(
RequestSession requestSession, List<String> requestOptions) {
- if (!isServiceCapableLocked(requestOptions)) {
+ if (!requestOptions.isEmpty() && !isServiceCapableLocked(requestOptions)) {
Log.i(TAG, "Service is not capable");
return null;
}
@@ -88,9 +88,7 @@
}
final RemoteCredentialService remoteService = new RemoteCredentialService(
getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
- ProviderSession providerSession =
- requestSession.initiateProviderSession(mInfo, remoteService);
- return providerSession;
+ return requestSession.initiateProviderSession(mInfo, remoteService);
}
/** Return true if at least one capability found. */
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
new file mode 100644
index 0000000..020552a
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -0,0 +1,119 @@
+/*
+ * 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.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.credentials.ClearCredentialStateException;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.CallingAppInfo;
+import android.service.credentials.ClearCredentialStateRequest;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * Central provider session that listens for provider callbacks, and maintains provider state.
+ *
+ * @hide
+ */
+public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
+ Void>
+ implements
+ RemoteCredentialService.ProviderCallbacks<Void> {
+ private static final String TAG = "ProviderClearSession";
+
+ private ClearCredentialStateException mProviderException;
+
+ /** Creates a new provider session to be used by the request session. */
+ @Nullable public static ProviderClearSession createNewSession(
+ Context context,
+ @UserIdInt int userId,
+ CredentialProviderInfo providerInfo,
+ ClearRequestSession clearRequestSession,
+ RemoteCredentialService remoteCredentialService) {
+ ClearCredentialStateRequest providerRequest =
+ createProviderRequest(
+ clearRequestSession.mClientRequest,
+ clearRequestSession.mClientCallingPackage);
+ return new ProviderClearSession(context, providerInfo, clearRequestSession, userId,
+ remoteCredentialService, providerRequest);
+ }
+
+ @Nullable
+ private static ClearCredentialStateRequest createProviderRequest(
+ android.credentials.ClearCredentialStateRequest clientRequest,
+ String clientCallingPackage
+ ) {
+ // TODO: Determine if provider needs to declare clear capability in manifest
+ return new ClearCredentialStateRequest(
+ new CallingAppInfo(clientCallingPackage, new ArraySet<>()),
+ clientRequest.getData());
+ }
+
+ public ProviderClearSession(Context context,
+ CredentialProviderInfo info,
+ ProviderInternalCallback callbacks,
+ int userId, RemoteCredentialService remoteCredentialService,
+ ClearCredentialStateRequest providerRequest) {
+ super(context, info, providerRequest, callbacks, userId, remoteCredentialService);
+ setStatus(Status.PENDING);
+ }
+
+ @Override
+ public void onProviderResponseSuccess(@Nullable Void response) {
+ Log.i(TAG, "in onProviderResponseSuccess");
+ mProviderResponseSet = true;
+ updateStatusAndInvokeCallback(Status.COMPLETE);
+ }
+
+ /** Called when the provider response resulted in a failure. */
+ @Override // Callback from the remote provider
+ public void onProviderResponseFailure(int errorCode, Exception exception) {
+ if (exception instanceof ClearCredentialStateException) {
+ mProviderException = (ClearCredentialStateException) exception;
+ }
+ updateStatusAndInvokeCallback(toStatus(errorCode));
+ }
+
+ /** Called when provider service dies. */
+ @Override // Callback from the remote provider
+ 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");
+ }
+ }
+
+ @Nullable
+ @Override
+ protected ProviderData prepareUiData() {
+ //Not applicable for clearCredential as response is not picked by the user
+ return null;
+ }
+
+ @Override
+ protected void onUiEntrySelected(String entryType, String entryId,
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ //Not applicable for clearCredential as response is not picked by the user
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 7ecae9d..93e816a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -51,6 +51,7 @@
@Nullable protected Credential mFinalCredentialResponse;
@NonNull protected final T mProviderRequest;
@Nullable protected R mProviderResponse;
+ @NonNull protected Boolean mProviderResponseSet = false;
@Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
@@ -84,7 +85,8 @@
*/
public static boolean isCompletionStatus(Status status) {
return status == Status.CREDENTIAL_RECEIVED_FROM_INTENT
- || status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION;
+ || status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION
+ || status == Status.COMPLETE;
}
/**
@@ -130,7 +132,8 @@
CREDENTIAL_RECEIVED_FROM_INTENT,
PENDING_INTENT_INVOKED,
CREDENTIAL_RECEIVED_FROM_SELECTION,
- SAVE_ENTRIES_RECEIVED, CANCELED
+ SAVE_ENTRIES_RECEIVED, CANCELED,
+ COMPLETE
}
/** Converts exception to a provider session status. */
@@ -183,6 +186,11 @@
return mProviderRequest;
}
+ /** Returns whether the provider response is set. */
+ protected Boolean isProviderResponseSet() {
+ return mProviderResponse != null || mProviderResponseSet;
+ }
+
/** Update the response state stored with the provider session. */
@Nullable protected R getProviderResponse() {
return mProviderResponse;
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 6049fd9..8cad6ac 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -21,6 +21,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.credentials.ClearCredentialStateException;
import android.credentials.CreateCredentialException;
import android.credentials.GetCredentialException;
import android.os.Handler;
@@ -30,10 +31,12 @@
import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.BeginGetCredentialRequest;
import android.service.credentials.BeginGetCredentialResponse;
+import android.service.credentials.ClearCredentialStateRequest;
import android.service.credentials.CredentialProviderErrors;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.IBeginCreateCredentialCallback;
import android.service.credentials.IBeginGetCredentialCallback;
+import android.service.credentials.IClearCredentialStateCallback;
import android.service.credentials.ICredentialProviderService;
import android.text.format.DateUtils;
import android.util.Log;
@@ -195,6 +198,53 @@
handleExecutionResponse(result, error, cancellationSink, callback)));
}
+ /** Main entry point to be called for executing a clearCredentialState 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 ProviderClearSession} class that maintains provider state
+ */
+ public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
+ ProviderCallbacks<Void> callback) {
+ Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
+ AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+ AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
+
+ CompletableFuture<Void> connectThenExecute =
+ postAsync(service -> {
+ CompletableFuture<Void> clearCredentialFuture =
+ new CompletableFuture<>();
+ ICancellationSignal cancellationSignal = service.onClearCredentialState(
+ request, new IClearCredentialStateCallback.Stub() {
+ @Override
+ public void onSuccess() {
+ Log.i(TAG, "In onSuccess onClearCredentialState "
+ + "in RemoteCredentialService");
+ clearCredentialFuture.complete(null);
+ }
+
+ @Override
+ public void onFailure(String errorType, CharSequence message) {
+ Log.i(TAG, "In onFailure in RemoteCredentialService");
+ String errorMsg = message == null ? "" :
+ String.valueOf(message);
+ clearCredentialFuture.completeExceptionally(
+ new ClearCredentialStateException(errorType, errorMsg));
+ }});
+ CompletableFuture<Void> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellationSignal);
+ } else {
+ cancellationSink.set(cancellationSignal);
+ }
+ return clearCredentialFuture;
+ }).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,
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 937fac9..6d7cb4c 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -144,7 +144,7 @@
mProviders.clear();
}
- private boolean isAnyProviderPending() {
+ boolean isAnyProviderPending() {
for (ProviderSession session : mProviders.values()) {
if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
return true;