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.annotation.NonNull; 20 import android.annotation.UserIdInt; 21 import android.app.PendingIntent; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.credentials.CredentialProviderInfo; 26 import android.credentials.ui.ProviderData; 27 import android.credentials.ui.UserSelectionDialogResult; 28 import android.os.Binder; 29 import android.os.CancellationSignal; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.Looper; 33 import android.os.RemoteException; 34 import android.os.UserHandle; 35 import android.service.credentials.CallingAppInfo; 36 import android.util.Slog; 37 38 import com.android.internal.R; 39 import com.android.server.credentials.metrics.ApiName; 40 import com.android.server.credentials.metrics.ApiStatus; 41 import com.android.server.credentials.metrics.ProviderSessionMetric; 42 import com.android.server.credentials.metrics.ProviderStatusForMetrics; 43 import com.android.server.credentials.metrics.RequestSessionMetric; 44 45 import java.util.ArrayList; 46 import java.util.Map; 47 import java.util.Set; 48 import java.util.concurrent.ConcurrentHashMap; 49 50 /** 51 * Base class of a request session, that listens to UI events. This class must be extended 52 * every time a new response type is expected from the providers. 53 */ 54 abstract class RequestSession<T, U, V> implements CredentialManagerUi.CredentialManagerUiCallback { 55 private static final String TAG = "RequestSession"; 56 57 public interface SessionLifetime { 58 /** Called when the user makes a selection. */ onFinishRequestSession(@serIdInt int userId, IBinder token)59 void onFinishRequestSession(@UserIdInt int userId, IBinder token); 60 } 61 62 // TODO: Revise access levels of attributes 63 @NonNull 64 protected final T mClientRequest; 65 @NonNull 66 protected final U mClientCallback; 67 @NonNull 68 protected final IBinder mRequestId; 69 @NonNull 70 protected final Context mContext; 71 @NonNull 72 protected final CredentialManagerUi mCredentialManagerUi; 73 @NonNull 74 protected final String mRequestType; 75 @NonNull 76 protected final Handler mHandler; 77 @UserIdInt 78 protected final int mUserId; 79 80 protected final int mUniqueSessionInteger; 81 private final int mCallingUid; 82 @NonNull 83 protected final CallingAppInfo mClientAppInfo; 84 @NonNull 85 protected final CancellationSignal mCancellationSignal; 86 87 protected final Map<String, ProviderSession> mProviders = new ConcurrentHashMap<>(); 88 protected final RequestSessionMetric mRequestSessionMetric; 89 protected final String mHybridService; 90 91 protected final Object mLock; 92 93 protected final SessionLifetime mSessionCallback; 94 95 private final Set<ComponentName> mEnabledProviders; 96 97 protected PendingIntent mPendingIntent; 98 99 @NonNull 100 protected RequestSessionStatus mRequestSessionStatus = 101 RequestSessionStatus.IN_PROGRESS; 102 103 /** The status in which a given request session is. */ 104 enum RequestSessionStatus { 105 /** Request is in progress. This is the status a request session is instantiated with. */ 106 IN_PROGRESS, 107 /** Request has been cancelled by the developer. */ 108 CANCELLED, 109 /** Request is complete. */ 110 COMPLETE 111 } 112 RequestSession(@onNull Context context, RequestSession.SessionLifetime sessionCallback, Object lock, @UserIdInt int userId, int callingUid, @NonNull T clientRequest, U clientCallback, @NonNull String requestType, CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders, CancellationSignal cancellationSignal, long timestampStarted)113 protected RequestSession(@NonNull Context context, 114 RequestSession.SessionLifetime sessionCallback, 115 Object lock, @UserIdInt int userId, int callingUid, 116 @NonNull T clientRequest, U clientCallback, 117 @NonNull String requestType, 118 CallingAppInfo callingAppInfo, 119 Set<ComponentName> enabledProviders, 120 CancellationSignal cancellationSignal, long timestampStarted) { 121 mContext = context; 122 mLock = lock; 123 mSessionCallback = sessionCallback; 124 mUserId = userId; 125 mCallingUid = callingUid; 126 mClientRequest = clientRequest; 127 mClientCallback = clientCallback; 128 mRequestType = requestType; 129 mClientAppInfo = callingAppInfo; 130 mEnabledProviders = enabledProviders; 131 mCancellationSignal = cancellationSignal; 132 mHandler = new Handler(Looper.getMainLooper(), null, true); 133 mRequestId = new Binder(); 134 mCredentialManagerUi = new CredentialManagerUi(mContext, 135 mUserId, this, mEnabledProviders); 136 mHybridService = context.getResources().getString( 137 R.string.config_defaultCredentialManagerHybridService); 138 mUniqueSessionInteger = MetricUtilities.getHighlyUniqueInteger(); 139 mRequestSessionMetric = new RequestSessionMetric(mUniqueSessionInteger, 140 MetricUtilities.getHighlyUniqueInteger()); 141 mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, 142 mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType)); 143 setCancellationListener(); 144 } 145 setCancellationListener()146 private void setCancellationListener() { 147 mCancellationSignal.setOnCancelListener( 148 () -> { 149 boolean isUiActive = maybeCancelUi(); 150 finishSession(!isUiActive); 151 } 152 ); 153 } 154 maybeCancelUi()155 private boolean maybeCancelUi() { 156 if (mCredentialManagerUi.getStatus() 157 == CredentialManagerUi.UiStatus.USER_INTERACTION) { 158 final long originalCallingUidToken = Binder.clearCallingIdentity(); 159 try { 160 mContext.startActivityAsUser(mCredentialManagerUi.createCancelIntent( 161 mRequestId, mClientAppInfo.getPackageName()) 162 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), UserHandle.of(mUserId)); 163 return true; 164 } finally { 165 Binder.restoreCallingIdentity(originalCallingUidToken); 166 } 167 } 168 return false; 169 } 170 initiateProviderSession(CredentialProviderInfo providerInfo, RemoteCredentialService remoteCredentialService)171 public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo, 172 RemoteCredentialService remoteCredentialService); 173 launchUiWithProviderData(ArrayList<ProviderData> providerDataList)174 protected abstract void launchUiWithProviderData(ArrayList<ProviderData> providerDataList); 175 invokeClientCallbackSuccess(V response)176 protected abstract void invokeClientCallbackSuccess(V response) throws RemoteException; 177 invokeClientCallbackError(String errorType, String errorMsg)178 protected abstract void invokeClientCallbackError(String errorType, String errorMsg) throws 179 RemoteException; 180 addProviderSession(ComponentName componentName, ProviderSession providerSession)181 public void addProviderSession(ComponentName componentName, ProviderSession providerSession) { 182 mProviders.put(componentName.flattenToString(), providerSession); 183 } 184 185 // UI callbacks 186 187 @Override // from CredentialManagerUiCallbacks onUiSelection(UserSelectionDialogResult selection)188 public void onUiSelection(UserSelectionDialogResult selection) { 189 if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { 190 Slog.w(TAG, "Request has already been completed. This is strange."); 191 return; 192 } 193 if (isSessionCancelled()) { 194 finishSession(/*propagateCancellation=*/true); 195 return; 196 } 197 String providerId = selection.getProviderId(); 198 ProviderSession providerSession = mProviders.get(providerId); 199 if (providerSession == null) { 200 Slog.w(TAG, "providerSession not found in onUiSelection. This is strange."); 201 return; 202 } 203 ProviderSessionMetric providerSessionMetric = providerSession.mProviderSessionMetric; 204 int initialAuthMetricsProvider = providerSessionMetric.getBrowsedAuthenticationMetric() 205 .size(); 206 mRequestSessionMetric.collectMetricPerBrowsingSelect(selection, 207 providerSession.mProviderSessionMetric.getCandidatePhasePerProviderMetric()); 208 providerSession.onUiEntrySelected(selection.getEntryKey(), 209 selection.getEntrySubkey(), selection.getPendingIntentProviderResponse()); 210 int numAuthPerProvider = providerSessionMetric.getBrowsedAuthenticationMetric().size(); 211 boolean authMetricLogged = (numAuthPerProvider - initialAuthMetricsProvider) == 1; 212 if (authMetricLogged) { 213 mRequestSessionMetric.logAuthEntry( 214 providerSession.mProviderSessionMetric.getBrowsedAuthenticationMetric() 215 .get(numAuthPerProvider - 1)); 216 } 217 } 218 finishSession(boolean propagateCancellation)219 protected void finishSession(boolean propagateCancellation) { 220 Slog.i(TAG, "finishing session with propagateCancellation " + propagateCancellation); 221 if (propagateCancellation) { 222 mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession); 223 } 224 mRequestSessionStatus = RequestSessionStatus.COMPLETE; 225 mProviders.clear(); 226 clearRequestSessionLocked(); 227 } 228 cancelExistingPendingIntent()229 void cancelExistingPendingIntent() { 230 if (mPendingIntent != null) { 231 try { 232 mPendingIntent.cancel(); 233 mPendingIntent = null; 234 } catch (Exception e) { 235 Slog.e(TAG, "Unable to cancel existing pending intent", e); 236 } 237 } 238 } 239 clearRequestSessionLocked()240 private void clearRequestSessionLocked() { 241 synchronized (mLock) { 242 mSessionCallback.onFinishRequestSession(mUserId, mRequestId); 243 } 244 } 245 isAnyProviderPending()246 protected boolean isAnyProviderPending() { 247 for (ProviderSession session : mProviders.values()) { 248 if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) { 249 return true; 250 } 251 } 252 return false; 253 } 254 isSessionCancelled()255 protected boolean isSessionCancelled() { 256 return mCancellationSignal.isCanceled(); 257 } 258 259 /** 260 * Returns true if at least one provider is ready for UI invocation, and no 261 * provider is pending a response. 262 */ isUiInvocationNeeded()263 protected boolean isUiInvocationNeeded() { 264 for (ProviderSession session : mProviders.values()) { 265 if (ProviderSession.isUiInvokingStatus(session.getStatus())) { 266 return true; 267 } else if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) { 268 return false; 269 } 270 } 271 return false; 272 } 273 getProviderDataAndInitiateUi()274 void getProviderDataAndInitiateUi() { 275 ArrayList<ProviderData> providerDataList = getProviderDataForUi(); 276 if (!providerDataList.isEmpty()) { 277 launchUiWithProviderData(providerDataList); 278 } 279 } 280 281 @NonNull getProviderDataForUi()282 protected ArrayList<ProviderData> getProviderDataForUi() { 283 Slog.i(TAG, "For ui, provider data size: " + mProviders.size()); 284 ArrayList<ProviderData> providerDataList = new ArrayList<>(); 285 mRequestSessionMetric.logCandidatePhaseMetrics(mProviders); 286 287 if (isSessionCancelled()) { 288 finishSession(/*propagateCancellation=*/true); 289 return providerDataList; 290 } 291 292 for (ProviderSession session : mProviders.values()) { 293 ProviderData providerData = session.prepareUiData(); 294 if (providerData != null) { 295 providerDataList.add(providerData); 296 } 297 } 298 return providerDataList; 299 } 300 301 /** 302 * Allows subclasses to directly finalize the call and set closing metrics on response. 303 * 304 * @param response the response associated with the API call that just completed 305 */ respondToClientWithResponseAndFinish(V response)306 protected void respondToClientWithResponseAndFinish(V response) { 307 mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); 308 mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*has_exception=*/ false, 309 ProviderStatusForMetrics.FINAL_SUCCESS); 310 if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { 311 Slog.w(TAG, "Request has already been completed. This is strange."); 312 return; 313 } 314 if (isSessionCancelled()) { 315 mRequestSessionMetric.logApiCalledAtFinish( 316 /*apiStatus=*/ ApiStatus.CLIENT_CANCELED.getMetricCode()); 317 finishSession(/*propagateCancellation=*/true); 318 return; 319 } 320 try { 321 invokeClientCallbackSuccess(response); 322 mRequestSessionMetric.logApiCalledAtFinish( 323 /*apiStatus=*/ ApiStatus.SUCCESS.getMetricCode()); 324 } catch (RemoteException e) { 325 mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( 326 /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); 327 Slog.e(TAG, "Issue while responding to client with a response : " + e); 328 mRequestSessionMetric.logApiCalledAtFinish( 329 /*apiStatus=*/ ApiStatus.FAILURE.getMetricCode()); 330 } 331 finishSession(/*propagateCancellation=*/false); 332 } 333 334 /** 335 * Allows subclasses to directly finalize the call and set closing metrics on error completion. 336 * 337 * @param errorType the type of error given back in the flow 338 * @param errorMsg the error message given back in the flow 339 */ respondToClientWithErrorAndFinish(String errorType, String errorMsg)340 protected void respondToClientWithErrorAndFinish(String errorType, String errorMsg) { 341 mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); 342 mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( 343 /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); 344 if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { 345 Slog.w(TAG, "Request has already been completed. This is strange."); 346 return; 347 } 348 if (isSessionCancelled()) { 349 mRequestSessionMetric.logApiCalledAtFinish( 350 /*apiStatus=*/ ApiStatus.CLIENT_CANCELED.getMetricCode()); 351 finishSession(/*propagateCancellation=*/true); 352 return; 353 } 354 355 try { 356 invokeClientCallbackError(errorType, errorMsg); 357 } catch (RemoteException e) { 358 Slog.e(TAG, "Issue while responding to client with error : " + e); 359 } 360 boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING); 361 mRequestSessionMetric.logFailureOrUserCancel(isUserCanceled); 362 finishSession(/*propagateCancellation=*/false); 363 } 364 365 /** 366 * Reveals if a certain provider is primary after ensuring it exists at all in the designated 367 * provider info. 368 * 369 * @param componentName used to identify the provider we want to check primary status for 370 */ isPrimaryProviderViaProviderInfo(ComponentName componentName)371 protected boolean isPrimaryProviderViaProviderInfo(ComponentName componentName) { 372 var chosenProviderSession = mProviders.get(componentName.flattenToString()); 373 return chosenProviderSession != null && chosenProviderSession.mProviderInfo != null 374 && chosenProviderSession.mProviderInfo.isPrimary(); 375 } 376 } 377