1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.credentials; 18 19 import android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.credentials.CreateCredentialException; 25 import android.credentials.CreateCredentialRequest; 26 import android.credentials.CreateCredentialResponse; 27 import android.credentials.CredentialManager; 28 import android.credentials.CredentialProviderInfo; 29 import android.credentials.ICreateCredentialCallback; 30 import android.credentials.ui.ProviderData; 31 import android.credentials.ui.RequestInfo; 32 import android.os.CancellationSignal; 33 import android.os.RemoteException; 34 import android.service.credentials.CallingAppInfo; 35 import android.service.credentials.PermissionUtils; 36 import android.util.Slog; 37 38 import com.android.server.credentials.metrics.ProviderStatusForMetrics; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.Set; 43 44 /** 45 * Central session for a single {@link CredentialManager#createCredential} request. 46 * This class listens to the responses from providers, and the UX app, and updates the 47 * provider(s) state maintained in {@link ProviderCreateSession}. 48 */ 49 public final class CreateRequestSession extends RequestSession<CreateCredentialRequest, 50 ICreateCredentialCallback, CreateCredentialResponse> 51 implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> { 52 private static final String TAG = "CreateRequestSession"; 53 private final Set<ComponentName> mPrimaryProviders; 54 CreateRequestSession(@onNull Context context, RequestSession.SessionLifetime sessionCallback, Object lock, int userId, int callingUid, CreateCredentialRequest request, ICreateCredentialCallback callback, CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders, Set<ComponentName> primaryProviders, CancellationSignal cancellationSignal, long startedTimestamp)55 CreateRequestSession(@NonNull Context context, RequestSession.SessionLifetime sessionCallback, 56 Object lock, int userId, int callingUid, 57 CreateCredentialRequest request, 58 ICreateCredentialCallback callback, 59 CallingAppInfo callingAppInfo, 60 Set<ComponentName> enabledProviders, 61 Set<ComponentName> primaryProviders, 62 CancellationSignal cancellationSignal, 63 long startedTimestamp) { 64 super(context, sessionCallback, lock, userId, callingUid, request, callback, 65 RequestInfo.TYPE_CREATE, 66 callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp); 67 mRequestSessionMetric.collectCreateFlowInitialMetricInfo( 68 /*origin=*/request.getOrigin() != null, request); 69 mPrimaryProviders = primaryProviders; 70 } 71 72 /** 73 * Creates a new provider session, and adds it to list of providers that are contributing to 74 * this request session. 75 * 76 * @return the provider session that was started 77 */ 78 @Override 79 @Nullable initiateProviderSession(CredentialProviderInfo providerInfo, RemoteCredentialService remoteCredentialService)80 public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo, 81 RemoteCredentialService remoteCredentialService) { 82 ProviderCreateSession providerCreateSession = ProviderCreateSession 83 .createNewSession(mContext, mUserId, providerInfo, 84 this, remoteCredentialService); 85 if (providerCreateSession != null) { 86 Slog.i(TAG, "Provider session created and " 87 + "being added for: " + providerInfo.getComponentName()); 88 mProviders.put(providerCreateSession.getComponentName().flattenToString(), 89 providerCreateSession); 90 } 91 return providerCreateSession; 92 } 93 94 @Override launchUiWithProviderData(ArrayList<ProviderData> providerDataList)95 protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) { 96 mRequestSessionMetric.collectUiCallStartTime(System.nanoTime()); 97 mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION); 98 cancelExistingPendingIntent(); 99 try { 100 List<String> flattenedPrimaryProviders = new ArrayList<>(); 101 for (ComponentName cn : mPrimaryProviders) { 102 flattenedPrimaryProviders.add(cn.flattenToString()); 103 } 104 105 mPendingIntent = mCredentialManagerUi.createPendingIntent( 106 RequestInfo.newCreateRequestInfo( 107 mRequestId, mClientRequest, 108 mClientAppInfo.getPackageName(), 109 PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(), 110 Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS), 111 /*defaultProviderId=*/flattenedPrimaryProviders), 112 providerDataList); 113 mClientCallback.onPendingIntent(mPendingIntent); 114 } catch (RemoteException e) { 115 mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false); 116 mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED); 117 respondToClientWithErrorAndFinish( 118 CreateCredentialException.TYPE_UNKNOWN, 119 "Unable to invoke selector"); 120 } 121 } 122 123 @Override invokeClientCallbackSuccess(CreateCredentialResponse response)124 protected void invokeClientCallbackSuccess(CreateCredentialResponse response) 125 throws RemoteException { 126 mClientCallback.onResponse(response); 127 } 128 129 @Override invokeClientCallbackError(String errorType, String errorMsg)130 protected void invokeClientCallbackError(String errorType, String errorMsg) 131 throws RemoteException { 132 mClientCallback.onError(errorType, errorMsg); 133 } 134 135 @Override onFinalResponseReceived(ComponentName componentName, @Nullable CreateCredentialResponse response)136 public void onFinalResponseReceived(ComponentName componentName, 137 @Nullable CreateCredentialResponse response) { 138 Slog.i(TAG, "Final credential received from: " + componentName.flattenToString()); 139 mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); 140 mRequestSessionMetric.updateMetricsOnResponseReceived(mProviders, componentName, 141 isPrimaryProviderViaProviderInfo(componentName)); 142 if (response != null) { 143 mRequestSessionMetric.collectChosenProviderStatus( 144 ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); 145 respondToClientWithResponseAndFinish(response); 146 } else { 147 mRequestSessionMetric.collectChosenProviderStatus( 148 ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); 149 String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; 150 mRequestSessionMetric.collectFrameworkException(exception); 151 respondToClientWithErrorAndFinish(exception, 152 "Invalid response"); 153 } 154 } 155 156 @Override onFinalErrorReceived(ComponentName componentName, String errorType, String message)157 public void onFinalErrorReceived(ComponentName componentName, String errorType, 158 String message) { 159 respondToClientWithErrorAndFinish(errorType, message); 160 } 161 162 @Override onUiCancellation(boolean isUserCancellation)163 public void onUiCancellation(boolean isUserCancellation) { 164 String exception = CreateCredentialException.TYPE_USER_CANCELED; 165 String message = "User cancelled the selector"; 166 if (!isUserCancellation) { 167 exception = CreateCredentialException.TYPE_INTERRUPTED; 168 message = "The UI was interrupted - please try again."; 169 } 170 mRequestSessionMetric.collectFrameworkException(exception); 171 respondToClientWithErrorAndFinish(exception, message); 172 } 173 174 @Override onUiSelectorInvocationFailure()175 public void onUiSelectorInvocationFailure() { 176 String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; 177 mRequestSessionMetric.collectFrameworkException(exception); 178 respondToClientWithErrorAndFinish(exception, 179 "No create options available."); 180 } 181 182 @Override onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source)183 public void onProviderStatusChanged(ProviderSession.Status status, 184 ComponentName componentName, ProviderSession.CredentialsSource source) { 185 Slog.i(TAG, "Provider status changed: " + status + ", and source: " + source); 186 // If all provider responses have been received, we can either need the UI, 187 // or we need to respond with error. The only other case is the entry being 188 // selected after the UI has been invoked which has a separate code path. 189 if (!isAnyProviderPending()) { 190 if (isUiInvocationNeeded()) { 191 Slog.i(TAG, "Provider status changed - ui invocation is needed"); 192 getProviderDataAndInitiateUi(); 193 } else { 194 String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; 195 mRequestSessionMetric.collectFrameworkException(exception); 196 respondToClientWithErrorAndFinish(exception, 197 "No create options available."); 198 } 199 } 200 } 201 } 202