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;