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

Commit 24f37066 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement logic for getCandidateCredentials API" into main

parents b5cc1567 dda3fda0
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;
@@ -474,13 +474,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
@@ -900,7 +942,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