Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dda3fda0 authored by Reema Bajwa's avatar Reema Bajwa
Browse files

Implement logic for getCandidateCredentials API

All new classes are hidden

Bug: 299321990
Test: Built & deployed locally

Change-Id: Iffa808dee493319abb1f1db7a3b6f7c96d822d6d
parent 8ccd1ee3
Loading
Loading
Loading
Loading
+22 −2
Original line number Diff line number Diff line
@@ -131,15 +131,35 @@ public final class CredentialManager {
    @Hide
    public void getCandidateCredentials(
            @NonNull GetCredentialRequest request,
            @NonNull String callingPackage,
            @Nullable CancellationSignal cancellationSignal,
            @CallbackExecutor @NonNull Executor executor,
            @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
            @NonNull OutcomeReceiver<GetCandidateCredentialsResponse,
                    GetCandidateCredentialsException> callback
    ) {
        requireNonNull(request, "request must not be null");
        requireNonNull(callingPackage, "callingPackage must not be null");
        requireNonNull(executor, "executor must not be null");
        requireNonNull(callback, "callback must not be null");

        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
            Log.w(TAG, "getCredential already canceled");
            Log.w(TAG, "getCandidateCredentials already canceled");
            return;
        }

        ICancellationSignal cancelRemote = null;
        try {
            cancelRemote =
                    mService.getCandidateCredentials(
                            request,
                            new GetCandidateCredentialsTransport(executor, callback),
                            mContext.getOpPackageName());
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }

        if (cancellationSignal != null && cancelRemote != null) {
            cancellationSignal.setRemote(cancelRemote);
        }
    }

+32 −1
Original line number Diff line number Diff line
@@ -17,9 +17,17 @@
package android.credentials;

import android.annotation.Hide;
import android.annotation.NonNull;
import android.credentials.ui.GetCredentialProviderData;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.internal.util.AnnotationValidations;
import com.android.internal.util.Preconditions;

import java.util.ArrayList;
import java.util.List;

/**
 * A list of candidate credentials.
 *
@@ -28,11 +36,34 @@ import android.os.Parcelable;
@Hide
public final class GetCandidateCredentialsResponse implements Parcelable {
    // TODO(b/299321990): Add members

    @NonNull
    private final List<GetCredentialProviderData> mCandidateProviderDataList;

    /**
     * @hide
     */
    @Hide
    public GetCandidateCredentialsResponse(
            List<GetCredentialProviderData> candidateProviderDataList
    ) {
        Preconditions.checkCollectionNotEmpty(
                candidateProviderDataList,
                /*valueName=*/ "candidateProviderDataList");
        mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
    }

    protected GetCandidateCredentialsResponse(Parcel in) {
        List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
        in.readTypedList(candidateProviderDataList, GetCredentialProviderData.CREATOR);
        mCandidateProviderDataList = candidateProviderDataList;

        AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList);
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeTypedList(mCandidateProviderDataList);
    }

    @Override
@@ -41,7 +72,7 @@ public final class GetCandidateCredentialsResponse implements Parcelable {
    }

    public static final Creator<GetCandidateCredentialsResponse> CREATOR =
            new Creator<GetCandidateCredentialsResponse>() {
            new Creator<>() {
                @Override
                public GetCandidateCredentialsResponse createFromParcel(Parcel in) {
                    return new GetCandidateCredentialsResponse(in);
+1 −1
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ interface ICredentialManager {

    @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);

    @nullable ICancellationSignal getCandidateCredentials(in GetCandidateCredentialsRequest request, in IGetCandidateCredentialsCallback callback, String callingPackage);
    @nullable ICancellationSignal getCandidateCredentials(in GetCredentialRequest request, in IGetCandidateCredentialsCallback callback, String callingPackage);

    @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);

+58 −15
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialRequest;
import android.credentials.CredentialOption;
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCandidateCredentialsRequest;
import android.credentials.GetCandidateCredentialsException;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialStateCallback;
@@ -464,13 +464,55 @@ public final class CredentialManagerService
    final class CredentialManagerServiceStub extends ICredentialManager.Stub {
        @Override
        public ICancellationSignal getCandidateCredentials(
                GetCandidateCredentialsRequest request,
                GetCredentialRequest request,
                IGetCandidateCredentialsCallback callback,
                final String callingPackage) {
            Slog.i(TAG, "starting getCandidateCredentials with callingPackage: "
                    + callingPackage);
            // TODO(): Implement
            return CancellationSignal.createTransport();
            ICancellationSignal cancelTransport = CancellationSignal.createTransport();

            final int userId = UserHandle.getCallingUserId();
            final int callingUid = Binder.getCallingUid();

            // New request session, scoped for this request only.
            final GetCandidateRequestSession session =
                    new GetCandidateRequestSession(
                            getContext(),
                            mSessionManager,
                            mLock,
                            userId,
                            callingUid,
                            callback,
                            request,
                            constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
                            getEnabledProvidersForUser(userId),
                            CancellationSignal.fromTransport(cancelTransport)
                    );
            addSessionLocked(userId, session);

            List<ProviderSession> providerSessions =
                    initiateProviderSessions(
                            session,
                            request.getCredentialOptions().stream()
                                    .map(CredentialOption::getType)
                                    .collect(Collectors.toList()));

            if (providerSessions.isEmpty()) {
                try {
                    callback.onError(
                            GetCandidateCredentialsException.TYPE_NO_CREDENTIAL,
                            "No credentials available on this device.");
                } catch (RemoteException e) {
                    Slog.i(
                            TAG,
                            "Issue invoking onError on IGetCredentialCallback "
                                    + "callback: "
                                    + e.getMessage());
                }
            }

            invokeProviderSessions(providerSessions);
            return cancelTransport;
        }

        @Override
@@ -890,7 +932,8 @@ public final class CredentialManagerService

            Set<ComponentName> enabledProviders = new HashSet<>();
            String directValue = Settings.Secure.getStringForUser(
                mContext.getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE, resolvedUserId);
                    mContext.getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE,
                    resolvedUserId);

            if (!TextUtils.isEmpty(directValue)) {
                String[] components = directValue.split(":");
+157 −0
Original line number Diff line number Diff line
/*
 * 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.CredentialProviderInfo;
import android.credentials.GetCandidateCredentialsException;
import android.credentials.GetCandidateCredentialsResponse;
import android.credentials.GetCredentialRequest;
import android.credentials.IGetCandidateCredentialsCallback;
import android.credentials.ui.GetCredentialProviderData;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
import android.util.Slog;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Central session for a single getCandidateCredentials request. This class listens to the
 * responses from providers, and updates the provider(s) state.
 */
public class GetCandidateRequestSession extends RequestSession<GetCredentialRequest,
        IGetCandidateCredentialsCallback, GetCandidateCredentialsResponse>
        implements ProviderSession.ProviderInternalCallback<GetCandidateCredentialsResponse> {
    private static final String TAG = "GetCandidateRequestSession";

    public GetCandidateRequestSession(
            Context context, SessionLifetime sessionCallback,
            Object lock, int userId, int callingUid,
            IGetCandidateCredentialsCallback callback, GetCredentialRequest request,
            CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
            CancellationSignal cancellationSignal) {
        super(context, sessionCallback, lock, userId, callingUid, request, callback,
                RequestInfo.TYPE_GET, callingAppInfo, enabledProviders,
                cancellationSignal, 0L);
    }

    /**
     * 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) {
        ProviderGetSession providerGetCandidateSessions = ProviderGetSession
                .createNewSession(mContext, mUserId, providerInfo,
                        this, remoteCredentialService);
        if (providerGetCandidateSessions != null) {
            Slog.d(TAG, "In startProviderSession - provider session created and "
                    + "being added for: " + providerInfo.getComponentName());
            mProviders.put(providerGetCandidateSessions.getComponentName().flattenToString(),
                    providerGetCandidateSessions);
        }
        return providerGetCandidateSessions;
    }

    /**
     * Even though there is no UI involved, this is called when all providers are ready
     * in our current flow. Eventually can completely separate UI and non UI flows.
     */
    @Override
    protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
        if (providerDataList == null || providerDataList.isEmpty()) {
            respondToClientWithErrorAndFinish(
                    GetCandidateCredentialsException.TYPE_NO_CREDENTIAL,
                    "No credentials found");
            return;
        }

        List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
        for (ProviderData providerData : providerDataList) {
            candidateProviderDataList.add((GetCredentialProviderData) (providerData));
        }
        respondToClientWithResponseAndFinish(new GetCandidateCredentialsResponse(
                candidateProviderDataList));
    }

    @Override
    protected void invokeClientCallbackSuccess(GetCandidateCredentialsResponse response)
            throws RemoteException {
        mClientCallback.onResponse(response);
    }

    @Override
    protected void invokeClientCallbackError(String errorType, String errorMsg)
            throws RemoteException {
        mClientCallback.onError(errorType, errorMsg);
    }

    @Override
    public void onFinalErrorReceived(ComponentName componentName, String errorType,
            String message) {
        // Not applicable for session without UI
    }

    @Override
    public void onUiCancellation(boolean isUserCancellation) {
        // Not applicable for session without UI
    }

    @Override
    public void onUiSelectorInvocationFailure() {
        // Not applicable for session without UI
    }

    @Override
    public void onProviderStatusChanged(ProviderSession.Status status,
            ComponentName componentName, ProviderSession.CredentialsSource source) {
        Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);

        // For any other status, we check if all providers are done and then invoke UI if needed
        if (!isAnyProviderPending()) {
            // If all provider responses have been received, we can either need the UI,
            // or we need to respond with error. The only other case is the entry being
            // selected after the UI has been invoked which has a separate code path.
            if (isUiInvocationNeeded()) {
                Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
                getProviderDataAndInitiateUi();
            } else {
                respondToClientWithErrorAndFinish(
                        GetCandidateCredentialsException.TYPE_NO_CREDENTIAL,
                        "No credentials available");
            }
        }
    }

    @Override
    public void onFinalResponseReceived(ComponentName componentName,
            GetCandidateCredentialsResponse response) {
        // Not applicable for session without UI
    }
}
Loading