Loading core/java/android/credentials/CredentialManager.java +22 −2 Original line number Diff line number Diff line Loading @@ -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); } } Loading core/java/android/credentials/GetCandidateCredentialsResponse.java +32 −1 Original line number Diff line number Diff line Loading @@ -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. * Loading @@ -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 Loading @@ -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); Loading core/java/android/credentials/ICredentialManager.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -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); Loading services/credentials/java/com/android/server/credentials/CredentialManagerService.java +58 −15 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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(":"); Loading services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java 0 → 100644 +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
core/java/android/credentials/CredentialManager.java +22 −2 Original line number Diff line number Diff line Loading @@ -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); } } Loading
core/java/android/credentials/GetCandidateCredentialsResponse.java +32 −1 Original line number Diff line number Diff line Loading @@ -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. * Loading @@ -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 Loading @@ -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); Loading
core/java/android/credentials/ICredentialManager.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -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); Loading
services/credentials/java/com/android/server/credentials/CredentialManagerService.java +58 −15 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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(":"); Loading
services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java 0 → 100644 +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 } }