Loading services/credentials/java/com/android/server/credentials/ClearRequestSession.java +1 −1 Original line number Diff line number Diff line Loading @@ -72,7 +72,7 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta @Override // from provider session public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName) { ComponentName componentName, ProviderSession.CredentialsSource source) { Log.i(TAG, "in onStatusChanged with status: " + status); if (ProviderSession.isTerminatingStatus(status)) { Log.i(TAG, "in onStatusChanged terminating status"); Loading services/credentials/java/com/android/server/credentials/CreateRequestSession.java +1 −1 Original line number Diff line number Diff line Loading @@ -156,7 +156,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName) { ComponentName componentName, ProviderSession.CredentialsSource source) { Log.i(TAG, "in onProviderStatusChanged with status: " + status); // 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 Loading services/credentials/java/com/android/server/credentials/CredentialManagerService.java +43 −85 Original line number Diff line number Diff line Loading @@ -438,16 +438,12 @@ public final class CredentialManagerService + callingPackage); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); if (request.getOrigin() != null) { // Check privileged permissions mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null); } enforcePermissionForAllowedProviders(request); final int userId = UserHandle.getCallingUserId(); final int callingUid = Binder.getCallingUid(); enforceCallingPackage(callingPackage, callingUid); validateGetCredentialRequest(request); // New request session, scoped for this request only. final GetRequestSession session = new GetRequestSession( Loading @@ -460,7 +456,24 @@ public final class CredentialManagerService CancellationSignal.fromTransport(cancelTransport), timestampBegan); processGetCredential(request, callback, session); List<ProviderSession> providerSessions = prepareProviderSessions(request, session); if (providerSessions.isEmpty()) { try { callback.onError( GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available on this device."); } catch (RemoteException e) { Log.i( TAG, "Issue invoking onError on IGetCredentialCallback " + "callback: " + e.getMessage()); } } invokeProviderSessions(providerSessions); return cancelTransport; } Loading Loading @@ -488,75 +501,22 @@ public final class CredentialManagerService getContext(), userId, callingUid, prepareGetCredentialCallback, getCredentialCallback, request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), CancellationSignal.fromTransport(cancelTransport), timestampBegan); timestampBegan, prepareGetCredentialCallback); processGetCredential(request, prepareGetCredentialCallback, session); return cancelTransport; } private void processGetCredential( GetCredentialRequest request, IPrepareGetCredentialCallback callback, PrepareGetRequestSession session) { List<ProviderSession> providerSessions; if (isCredentialDescriptionApiEnabled()) { List<CredentialOption> optionsThatRequireActiveCredentials = request.getCredentialOptions().stream() .filter(credentialOption -> credentialOption .getCredentialRetrievalData() .getStringArrayList( CredentialOption .SUPPORTED_ELEMENT_KEYS) != null) .toList(); List<CredentialOption> optionsThatDoNotRequireActiveCredentials = request.getCredentialOptions().stream() .filter(credentialOption -> credentialOption .getCredentialRetrievalData() .getStringArrayList( CredentialOption .SUPPORTED_ELEMENT_KEYS) == null) .toList(); List<ProviderSession> sessionsWithoutRemoteService = initiateProviderSessionsWithActiveContainers( session, getFilteredResultFromRegistry(optionsThatRequireActiveCredentials)); List<ProviderSession> sessionsWithRemoteService = initiateProviderSessions( session, optionsThatDoNotRequireActiveCredentials.stream() .map(CredentialOption::getType) .collect(Collectors.toList())); Set<ProviderSession> all = new LinkedHashSet<>(); all.addAll(sessionsWithRemoteService); all.addAll(sessionsWithoutRemoteService); providerSessions = new ArrayList<>(all); } else { // Initiate all provider sessions providerSessions = initiateProviderSessions( session, request.getCredentialOptions().stream() .map(CredentialOption::getType) .collect(Collectors.toList())); } List<ProviderSession> providerSessions = prepareProviderSessions(request, session); if (providerSessions.isEmpty()) { try { // TODO: fix callback.onResponse(new PrepareGetCredentialResponseInternal( false, null, false, false, null)); prepareGetCredentialCallback.onResponse( new PrepareGetCredentialResponseInternal( false, null, false, false, null)); } catch (RemoteException e) { Log.i( TAG, Loading @@ -566,14 +526,13 @@ public final class CredentialManagerService } } finalizeAndEmitInitialPhaseMetric(session); // TODO(b/271135048) - May still be worth emitting in the empty cases above. providerSessions.forEach(ProviderSession::invokeSession); invokeProviderSessions(providerSessions); return cancelTransport; } private void processGetCredential( private List<ProviderSession> prepareProviderSessions( GetCredentialRequest request, IGetCredentialCallback callback, GetRequestSession session) { List<ProviderSession> providerSessions; Loading Loading @@ -623,21 +582,12 @@ public final class CredentialManagerService .collect(Collectors.toList())); } if (providerSessions.isEmpty()) { try { callback.onError( GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available on this device."); } catch (RemoteException e) { Slog.e( TAG, "Issue invoking onError on IGetCredentialCallback " + "callback: ", e); } } finalizeAndEmitInitialPhaseMetric(session); // TODO(b/271135048) - May still be worth emitting in the empty cases above. return providerSessions; } private void invokeProviderSessions(List<ProviderSession> providerSessions) { providerSessions.forEach(ProviderSession::invokeSession); } Loading Loading @@ -936,6 +886,14 @@ public final class CredentialManagerService } } private void validateGetCredentialRequest(GetCredentialRequest request) { if (request.getOrigin() != null) { // Check privileged permissions mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null); } enforcePermissionForAllowedProviders(request); } private void enforcePermissionForAllowedProviders(GetCredentialRequest request) { boolean containsAllowedProviders = request.getCredentialOptions() .stream() Loading services/credentials/java/com/android/server/credentials/GetRequestSession.java +4 −3 Original line number Diff line number Diff line Loading @@ -151,8 +151,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName) { Log.i(TAG, "in onStatusChanged with status: " + status); ComponentName componentName, ProviderSession.CredentialsSource source) { Log.i(TAG, "in onStatusChanged with status: " + status + "and source: " + source); // Auth entry was selected, and it did not have any underlying credentials if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) { handleEmptyAuthenticationSelection(componentName); Loading @@ -173,7 +174,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, } } private void handleEmptyAuthenticationSelection(ComponentName componentName) { protected void handleEmptyAuthenticationSelection(ComponentName componentName) { // Update auth entry statuses across different provider sessions mProviders.keySet().forEach(key -> { ProviderGetSession session = (ProviderGetSession) mProviders.get(key); Loading services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java +44 −173 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * Copyright (C) 2023 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. Loading @@ -22,10 +22,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.credentials.CredentialOption; import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; import android.credentials.GetCredentialRequest; import android.credentials.GetCredentialResponse; import android.credentials.IGetCredentialCallback; import android.credentials.IPrepareGetCredentialCallback; import android.credentials.PrepareGetCredentialResponseInternal; Loading @@ -37,32 +34,29 @@ import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.service.credentials.PermissionUtils; import android.util.Log; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import android.util.Slog; import java.util.ArrayList; import java.util.Set; import java.util.stream.Collectors; /** * Central session for a single prepareGetCredentials request. This class listens to the * responses from providers, and the UX app, and updates the provider(S) state. * Central session for a single pendingGetCredential request. This class listens to the * responses from providers, and the UX app, and updates the provider(s) state. */ public class PrepareGetRequestSession extends RequestSession<GetCredentialRequest, IGetCredentialCallback, GetCredentialResponse> implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> { private static final String TAG = "GetRequestSession"; public class PrepareGetRequestSession extends GetRequestSession { private static final String TAG = "PrepareGetRequestSession"; private final IPrepareGetCredentialCallback mPrepareGetCredentialCallback; private boolean mIsInitialQuery = true; public PrepareGetRequestSession(Context context, int userId, int callingUid, IPrepareGetCredentialCallback prepareGetCredentialCallback, IGetCredentialCallback getCredCallback, GetCredentialRequest request, CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal, long startedTimestamp) { super(context, userId, callingUid, request, getCredCallback, RequestInfo.TYPE_GET, callingAppInfo, cancellationSignal, startedTimestamp); IGetCredentialCallback callback, GetCredentialRequest request, CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal, long startedTimestamp, IPrepareGetCredentialCallback prepareGetCredentialCallback) { super(context, userId, callingUid, callback, request, callingAppInfo, cancellationSignal, startedTimestamp); int numTypes = (request.getCredentialOptions().stream() .map(CredentialOption::getType).collect( Collectors.toSet())).size(); // Dedupe type strings Loading @@ -70,146 +64,53 @@ public class PrepareGetRequestSession extends RequestSession<GetCredentialReques mPrepareGetCredentialCallback = prepareGetCredentialCallback; } /** * 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 providerGetSession = ProviderGetSession .createNewSession(mContext, mUserId, providerInfo, this, remoteCredentialService); if (providerGetSession != null) { Log.i(TAG, "In startProviderSession - provider session created and being added"); mProviders.put(providerGetSession.getComponentName().flattenToString(), providerGetSession); } return providerGetSession; } @Override protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) { mRequestSessionMetric.collectUiCallStartTime(System.nanoTime()); try { mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent( RequestInfo.newGetRequestInfo( mRequestId, mClientRequest, mClientAppInfo.getPackageName()), providerDataList)); } catch (RemoteException e) { mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false); respondToClientWithErrorAndFinish( GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector"); } } @Override protected void invokeClientCallbackSuccess(GetCredentialResponse response) throws RemoteException { mClientCallback.onResponse(response); } @Override protected void invokeClientCallbackError(String errorType, String errorMsg) throws RemoteException { mClientCallback.onError(errorType, errorMsg); } @Override public void onFinalResponseReceived(ComponentName componentName, @Nullable GetCredentialResponse response) { Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get( componentName.flattenToString()).mProviderSessionMetric .getCandidatePhasePerProviderMetric()); if (response != null) { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); respondToClientWithResponseAndFinish(response); } else { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, "Invalid response from provider"); } } //TODO: Try moving the three error & response methods below to RequestSession to be shared // between get & create. @Override public void onFinalErrorReceived(ComponentName componentName, String errorType, String message) { respondToClientWithErrorAndFinish(errorType, message); } @Override public void onUiCancellation(boolean isUserCancellation) { if (isUserCancellation) { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED, "User cancelled the selector"); } else { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_INTERRUPTED, "The UI was interrupted - please try again."); } } @Override public void onUiSelectorInvocationFailure() { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available."); } @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName) { Log.i(TAG, "in onStatusChanged with status: " + status); // Auth entry was selected, and it did not have any underlying credentials if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) { handleEmptyAuthenticationSelection(componentName); public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { switch (source) { case REMOTE_PROVIDER: // Remote provider's status changed. We should check if all providers are done, and // if UI invocation is needed. if (isAnyProviderPending()) { // Waiting for a remote provider response return; } // 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 (mIsInitialQuery) { // First time in this state. UI shouldn't be invoked because developer wants to // punt it for later boolean hasQueryCandidatePermission = PermissionUtils.hasPermission( mContext, mClientAppInfo.getPackageName(), Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS); if (isUiInvocationNeeded()) { // To avoid extra computation, we only prepare the data at this point when we // know that UI invocation is needed ArrayList<ProviderData> providerData = getProviderDataForUi(); if (!providerData.isEmpty()) { constructPendingResponseAndInvokeCallback(hasQueryCandidatePermission, getCredentialResultTypes(hasQueryCandidatePermission), hasAuthenticationResults(providerData, hasQueryCandidatePermission), hasAuthenticationResults(providerData, hasQueryCandidatePermission), hasRemoteResults(providerData, hasQueryCandidatePermission), getUiIntent()); } else { constructEmptyPendingResponseAndInvokeCallback(hasQueryCandidatePermission); return; } } else { constructEmptyPendingResponseAndInvokeCallback(hasQueryCandidatePermission); } mIsInitialQuery = false; } else { // Not the first time. This could be a result of a user selection leading to a UI // invocation again. if (isUiInvocationNeeded()) { // We reach here if Ui invocation is not needed, or provider data is empty constructEmptyPendingResponseAndInvokeCallback( hasQueryCandidatePermission); break; case AUTH_ENTRY: // Status updated through a selected authentication entry. We don't need to // check on any other credential source and can process this result directly. if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) { // Update entry subtitle and re-invoke UI super.handleEmptyAuthenticationSelection(componentName); } else if (status == ProviderSession.Status.CREDENTIALS_RECEIVED) { getProviderDataAndInitiateUi(); } else { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available"); } } break; default: Slog.w(TAG, "Unexpected source"); break; } } Loading Loading @@ -294,34 +195,4 @@ public class PrepareGetRequestSession extends RequestSession<GetCredentialReques return null; } } private void handleEmptyAuthenticationSelection(ComponentName componentName) { // Update auth entry statuses across different provider sessions mProviders.keySet().forEach(key -> { ProviderGetSession session = (ProviderGetSession) mProviders.get(key); if (!session.mComponentName.equals(componentName)) { session.updateAuthEntriesStatusFromAnotherSession(); } }); // Invoke UI since it needs to show a snackbar if last auth entry, or a status on each // auth entries along with other valid entries getProviderDataAndInitiateUi(); // Respond to client if all auth entries are empty and nothing else to show on the UI if (providerDataContainsEmptyAuthEntriesOnly()) { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available"); } } private boolean providerDataContainsEmptyAuthEntriesOnly() { for (String key : mProviders.keySet()) { ProviderGetSession session = (ProviderGetSession) mProviders.get(key); if (!session.containsEmptyAuthEntriesOnly()) { return false; } } return true; } } Loading
services/credentials/java/com/android/server/credentials/ClearRequestSession.java +1 −1 Original line number Diff line number Diff line Loading @@ -72,7 +72,7 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta @Override // from provider session public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName) { ComponentName componentName, ProviderSession.CredentialsSource source) { Log.i(TAG, "in onStatusChanged with status: " + status); if (ProviderSession.isTerminatingStatus(status)) { Log.i(TAG, "in onStatusChanged terminating status"); Loading
services/credentials/java/com/android/server/credentials/CreateRequestSession.java +1 −1 Original line number Diff line number Diff line Loading @@ -156,7 +156,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName) { ComponentName componentName, ProviderSession.CredentialsSource source) { Log.i(TAG, "in onProviderStatusChanged with status: " + status); // 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 Loading
services/credentials/java/com/android/server/credentials/CredentialManagerService.java +43 −85 Original line number Diff line number Diff line Loading @@ -438,16 +438,12 @@ public final class CredentialManagerService + callingPackage); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); if (request.getOrigin() != null) { // Check privileged permissions mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null); } enforcePermissionForAllowedProviders(request); final int userId = UserHandle.getCallingUserId(); final int callingUid = Binder.getCallingUid(); enforceCallingPackage(callingPackage, callingUid); validateGetCredentialRequest(request); // New request session, scoped for this request only. final GetRequestSession session = new GetRequestSession( Loading @@ -460,7 +456,24 @@ public final class CredentialManagerService CancellationSignal.fromTransport(cancelTransport), timestampBegan); processGetCredential(request, callback, session); List<ProviderSession> providerSessions = prepareProviderSessions(request, session); if (providerSessions.isEmpty()) { try { callback.onError( GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available on this device."); } catch (RemoteException e) { Log.i( TAG, "Issue invoking onError on IGetCredentialCallback " + "callback: " + e.getMessage()); } } invokeProviderSessions(providerSessions); return cancelTransport; } Loading Loading @@ -488,75 +501,22 @@ public final class CredentialManagerService getContext(), userId, callingUid, prepareGetCredentialCallback, getCredentialCallback, request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), CancellationSignal.fromTransport(cancelTransport), timestampBegan); timestampBegan, prepareGetCredentialCallback); processGetCredential(request, prepareGetCredentialCallback, session); return cancelTransport; } private void processGetCredential( GetCredentialRequest request, IPrepareGetCredentialCallback callback, PrepareGetRequestSession session) { List<ProviderSession> providerSessions; if (isCredentialDescriptionApiEnabled()) { List<CredentialOption> optionsThatRequireActiveCredentials = request.getCredentialOptions().stream() .filter(credentialOption -> credentialOption .getCredentialRetrievalData() .getStringArrayList( CredentialOption .SUPPORTED_ELEMENT_KEYS) != null) .toList(); List<CredentialOption> optionsThatDoNotRequireActiveCredentials = request.getCredentialOptions().stream() .filter(credentialOption -> credentialOption .getCredentialRetrievalData() .getStringArrayList( CredentialOption .SUPPORTED_ELEMENT_KEYS) == null) .toList(); List<ProviderSession> sessionsWithoutRemoteService = initiateProviderSessionsWithActiveContainers( session, getFilteredResultFromRegistry(optionsThatRequireActiveCredentials)); List<ProviderSession> sessionsWithRemoteService = initiateProviderSessions( session, optionsThatDoNotRequireActiveCredentials.stream() .map(CredentialOption::getType) .collect(Collectors.toList())); Set<ProviderSession> all = new LinkedHashSet<>(); all.addAll(sessionsWithRemoteService); all.addAll(sessionsWithoutRemoteService); providerSessions = new ArrayList<>(all); } else { // Initiate all provider sessions providerSessions = initiateProviderSessions( session, request.getCredentialOptions().stream() .map(CredentialOption::getType) .collect(Collectors.toList())); } List<ProviderSession> providerSessions = prepareProviderSessions(request, session); if (providerSessions.isEmpty()) { try { // TODO: fix callback.onResponse(new PrepareGetCredentialResponseInternal( false, null, false, false, null)); prepareGetCredentialCallback.onResponse( new PrepareGetCredentialResponseInternal( false, null, false, false, null)); } catch (RemoteException e) { Log.i( TAG, Loading @@ -566,14 +526,13 @@ public final class CredentialManagerService } } finalizeAndEmitInitialPhaseMetric(session); // TODO(b/271135048) - May still be worth emitting in the empty cases above. providerSessions.forEach(ProviderSession::invokeSession); invokeProviderSessions(providerSessions); return cancelTransport; } private void processGetCredential( private List<ProviderSession> prepareProviderSessions( GetCredentialRequest request, IGetCredentialCallback callback, GetRequestSession session) { List<ProviderSession> providerSessions; Loading Loading @@ -623,21 +582,12 @@ public final class CredentialManagerService .collect(Collectors.toList())); } if (providerSessions.isEmpty()) { try { callback.onError( GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available on this device."); } catch (RemoteException e) { Slog.e( TAG, "Issue invoking onError on IGetCredentialCallback " + "callback: ", e); } } finalizeAndEmitInitialPhaseMetric(session); // TODO(b/271135048) - May still be worth emitting in the empty cases above. return providerSessions; } private void invokeProviderSessions(List<ProviderSession> providerSessions) { providerSessions.forEach(ProviderSession::invokeSession); } Loading Loading @@ -936,6 +886,14 @@ public final class CredentialManagerService } } private void validateGetCredentialRequest(GetCredentialRequest request) { if (request.getOrigin() != null) { // Check privileged permissions mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null); } enforcePermissionForAllowedProviders(request); } private void enforcePermissionForAllowedProviders(GetCredentialRequest request) { boolean containsAllowedProviders = request.getCredentialOptions() .stream() Loading
services/credentials/java/com/android/server/credentials/GetRequestSession.java +4 −3 Original line number Diff line number Diff line Loading @@ -151,8 +151,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName) { Log.i(TAG, "in onStatusChanged with status: " + status); ComponentName componentName, ProviderSession.CredentialsSource source) { Log.i(TAG, "in onStatusChanged with status: " + status + "and source: " + source); // Auth entry was selected, and it did not have any underlying credentials if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) { handleEmptyAuthenticationSelection(componentName); Loading @@ -173,7 +174,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, } } private void handleEmptyAuthenticationSelection(ComponentName componentName) { protected void handleEmptyAuthenticationSelection(ComponentName componentName) { // Update auth entry statuses across different provider sessions mProviders.keySet().forEach(key -> { ProviderGetSession session = (ProviderGetSession) mProviders.get(key); Loading
services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java +44 −173 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * Copyright (C) 2023 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. Loading @@ -22,10 +22,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.credentials.CredentialOption; import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; import android.credentials.GetCredentialRequest; import android.credentials.GetCredentialResponse; import android.credentials.IGetCredentialCallback; import android.credentials.IPrepareGetCredentialCallback; import android.credentials.PrepareGetCredentialResponseInternal; Loading @@ -37,32 +34,29 @@ import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.service.credentials.PermissionUtils; import android.util.Log; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import android.util.Slog; import java.util.ArrayList; import java.util.Set; import java.util.stream.Collectors; /** * Central session for a single prepareGetCredentials request. This class listens to the * responses from providers, and the UX app, and updates the provider(S) state. * Central session for a single pendingGetCredential request. This class listens to the * responses from providers, and the UX app, and updates the provider(s) state. */ public class PrepareGetRequestSession extends RequestSession<GetCredentialRequest, IGetCredentialCallback, GetCredentialResponse> implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> { private static final String TAG = "GetRequestSession"; public class PrepareGetRequestSession extends GetRequestSession { private static final String TAG = "PrepareGetRequestSession"; private final IPrepareGetCredentialCallback mPrepareGetCredentialCallback; private boolean mIsInitialQuery = true; public PrepareGetRequestSession(Context context, int userId, int callingUid, IPrepareGetCredentialCallback prepareGetCredentialCallback, IGetCredentialCallback getCredCallback, GetCredentialRequest request, CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal, long startedTimestamp) { super(context, userId, callingUid, request, getCredCallback, RequestInfo.TYPE_GET, callingAppInfo, cancellationSignal, startedTimestamp); IGetCredentialCallback callback, GetCredentialRequest request, CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal, long startedTimestamp, IPrepareGetCredentialCallback prepareGetCredentialCallback) { super(context, userId, callingUid, callback, request, callingAppInfo, cancellationSignal, startedTimestamp); int numTypes = (request.getCredentialOptions().stream() .map(CredentialOption::getType).collect( Collectors.toSet())).size(); // Dedupe type strings Loading @@ -70,146 +64,53 @@ public class PrepareGetRequestSession extends RequestSession<GetCredentialReques mPrepareGetCredentialCallback = prepareGetCredentialCallback; } /** * 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 providerGetSession = ProviderGetSession .createNewSession(mContext, mUserId, providerInfo, this, remoteCredentialService); if (providerGetSession != null) { Log.i(TAG, "In startProviderSession - provider session created and being added"); mProviders.put(providerGetSession.getComponentName().flattenToString(), providerGetSession); } return providerGetSession; } @Override protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) { mRequestSessionMetric.collectUiCallStartTime(System.nanoTime()); try { mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent( RequestInfo.newGetRequestInfo( mRequestId, mClientRequest, mClientAppInfo.getPackageName()), providerDataList)); } catch (RemoteException e) { mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false); respondToClientWithErrorAndFinish( GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector"); } } @Override protected void invokeClientCallbackSuccess(GetCredentialResponse response) throws RemoteException { mClientCallback.onResponse(response); } @Override protected void invokeClientCallbackError(String errorType, String errorMsg) throws RemoteException { mClientCallback.onError(errorType, errorMsg); } @Override public void onFinalResponseReceived(ComponentName componentName, @Nullable GetCredentialResponse response) { Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get( componentName.flattenToString()).mProviderSessionMetric .getCandidatePhasePerProviderMetric()); if (response != null) { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); respondToClientWithResponseAndFinish(response); } else { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, "Invalid response from provider"); } } //TODO: Try moving the three error & response methods below to RequestSession to be shared // between get & create. @Override public void onFinalErrorReceived(ComponentName componentName, String errorType, String message) { respondToClientWithErrorAndFinish(errorType, message); } @Override public void onUiCancellation(boolean isUserCancellation) { if (isUserCancellation) { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED, "User cancelled the selector"); } else { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_INTERRUPTED, "The UI was interrupted - please try again."); } } @Override public void onUiSelectorInvocationFailure() { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available."); } @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName) { Log.i(TAG, "in onStatusChanged with status: " + status); // Auth entry was selected, and it did not have any underlying credentials if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) { handleEmptyAuthenticationSelection(componentName); public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { switch (source) { case REMOTE_PROVIDER: // Remote provider's status changed. We should check if all providers are done, and // if UI invocation is needed. if (isAnyProviderPending()) { // Waiting for a remote provider response return; } // 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 (mIsInitialQuery) { // First time in this state. UI shouldn't be invoked because developer wants to // punt it for later boolean hasQueryCandidatePermission = PermissionUtils.hasPermission( mContext, mClientAppInfo.getPackageName(), Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS); if (isUiInvocationNeeded()) { // To avoid extra computation, we only prepare the data at this point when we // know that UI invocation is needed ArrayList<ProviderData> providerData = getProviderDataForUi(); if (!providerData.isEmpty()) { constructPendingResponseAndInvokeCallback(hasQueryCandidatePermission, getCredentialResultTypes(hasQueryCandidatePermission), hasAuthenticationResults(providerData, hasQueryCandidatePermission), hasAuthenticationResults(providerData, hasQueryCandidatePermission), hasRemoteResults(providerData, hasQueryCandidatePermission), getUiIntent()); } else { constructEmptyPendingResponseAndInvokeCallback(hasQueryCandidatePermission); return; } } else { constructEmptyPendingResponseAndInvokeCallback(hasQueryCandidatePermission); } mIsInitialQuery = false; } else { // Not the first time. This could be a result of a user selection leading to a UI // invocation again. if (isUiInvocationNeeded()) { // We reach here if Ui invocation is not needed, or provider data is empty constructEmptyPendingResponseAndInvokeCallback( hasQueryCandidatePermission); break; case AUTH_ENTRY: // Status updated through a selected authentication entry. We don't need to // check on any other credential source and can process this result directly. if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) { // Update entry subtitle and re-invoke UI super.handleEmptyAuthenticationSelection(componentName); } else if (status == ProviderSession.Status.CREDENTIALS_RECEIVED) { getProviderDataAndInitiateUi(); } else { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available"); } } break; default: Slog.w(TAG, "Unexpected source"); break; } } Loading Loading @@ -294,34 +195,4 @@ public class PrepareGetRequestSession extends RequestSession<GetCredentialReques return null; } } private void handleEmptyAuthenticationSelection(ComponentName componentName) { // Update auth entry statuses across different provider sessions mProviders.keySet().forEach(key -> { ProviderGetSession session = (ProviderGetSession) mProviders.get(key); if (!session.mComponentName.equals(componentName)) { session.updateAuthEntriesStatusFromAnotherSession(); } }); // Invoke UI since it needs to show a snackbar if last auth entry, or a status on each // auth entries along with other valid entries getProviderDataAndInitiateUi(); // Respond to client if all auth entries are empty and nothing else to show on the UI if (providerDataContainsEmptyAuthEntriesOnly()) { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available"); } } private boolean providerDataContainsEmptyAuthEntriesOnly() { for (String key : mProviders.keySet()) { ProviderGetSession session = (ProviderGetSession) mProviders.get(key); if (!session.containsEmptyAuthEntriesOnly()) { return false; } } return true; } }