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 package com.android.server.credentials; 17 18 import android.annotation.NonNull; 19 import android.app.PendingIntent; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.credentials.CredentialManager; 24 import android.credentials.CredentialProviderInfo; 25 import android.credentials.flags.Flags; 26 import android.credentials.selection.DisabledProviderData; 27 import android.credentials.selection.IntentCreationResult; 28 import android.credentials.selection.IntentFactory; 29 import android.credentials.selection.ProviderData; 30 import android.credentials.selection.RequestInfo; 31 import android.credentials.selection.UserSelectionDialogResult; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.IBinder; 35 import android.os.Looper; 36 import android.os.ResultReceiver; 37 import android.os.UserHandle; 38 import android.service.credentials.CredentialProviderInfoFactory; 39 40 import com.android.server.credentials.metrics.RequestSessionMetric; 41 42 import java.util.ArrayList; 43 import java.util.HashSet; 44 import java.util.List; 45 import java.util.Set; 46 import java.util.UUID; 47 48 /** Initiates the Credential Manager UI and receives results. */ 49 public class CredentialManagerUi { 50 51 private static final String SESSION_ID_TRACK_ONE = 52 "com.android.server.credentials.CredentialManagerUi.SESSION_ID_TRACK_ONE"; 53 private static final String SESSION_ID_TRACK_TWO = 54 "com.android.server.credentials.CredentialManagerUi.SESSION_ID_TRACK_TWO"; 55 56 @NonNull 57 private final CredentialManagerUiCallback mCallbacks; 58 @NonNull 59 private final Context mContext; 60 61 private final int mUserId; 62 63 private UiStatus mStatus; 64 65 private final Set<ComponentName> mEnabledProviders; 66 67 enum UiStatus { 68 IN_PROGRESS, 69 USER_INTERACTION, 70 NOT_STARTED, TERMINATED 71 } 72 73 @NonNull 74 private final ResultReceiver mResultReceiver = new ResultReceiver( 75 new Handler(Looper.getMainLooper())) { 76 @Override 77 protected void onReceiveResult(int resultCode, Bundle resultData) { 78 handleUiResult(resultCode, resultData); 79 } 80 }; 81 handleUiResult(int resultCode, Bundle resultData)82 private void handleUiResult(int resultCode, Bundle resultData) { 83 84 switch (resultCode) { 85 case UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION: 86 mStatus = UiStatus.IN_PROGRESS; 87 UserSelectionDialogResult selection = UserSelectionDialogResult 88 .fromResultData(resultData); 89 if (selection != null) { 90 mCallbacks.onUiSelection(selection); 91 } 92 break; 93 case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED: 94 95 mStatus = UiStatus.TERMINATED; 96 mCallbacks.onUiCancellation(/* isUserCancellation= */ true); 97 break; 98 case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS: 99 100 mStatus = UiStatus.TERMINATED; 101 mCallbacks.onUiCancellation(/* isUserCancellation= */ false); 102 break; 103 case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE: 104 mStatus = UiStatus.TERMINATED; 105 mCallbacks.onUiSelectorInvocationFailure(); 106 break; 107 default: 108 mStatus = UiStatus.IN_PROGRESS; 109 mCallbacks.onUiSelectorInvocationFailure(); 110 break; 111 } 112 } 113 114 /** Creates intent that is ot be invoked to cancel an in-progress UI session. */ createCancelIntent(IBinder requestId, String packageName)115 public Intent createCancelIntent(IBinder requestId, String packageName) { 116 return IntentFactory.createCancelUiIntent(mContext, requestId, 117 /*shouldShowCancellationUi=*/ true, packageName, mUserId); 118 } 119 120 /** 121 * Interface to be implemented by any class that wishes to get callbacks from the UI. 122 */ 123 public interface CredentialManagerUiCallback { 124 /** Called when the user makes a selection. */ onUiSelection(UserSelectionDialogResult selection)125 void onUiSelection(UserSelectionDialogResult selection); 126 127 /** Called when the UI is canceled without a successful provider result. */ onUiCancellation(boolean isUserCancellation)128 void onUiCancellation(boolean isUserCancellation); 129 130 /** Called when the selector UI fails to come up (mostly due to parsing issue today). */ onUiSelectorInvocationFailure()131 void onUiSelectorInvocationFailure(); 132 } 133 CredentialManagerUi(Context context, int userId, CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders)134 public CredentialManagerUi(Context context, int userId, 135 CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders) { 136 mContext = context; 137 mUserId = userId; 138 mCallbacks = callbacks; 139 mEnabledProviders = enabledProviders; 140 mStatus = UiStatus.IN_PROGRESS; 141 } 142 143 /** Set status for credential manager UI */ setStatus(UiStatus status)144 public void setStatus(UiStatus status) { 145 mStatus = status; 146 } 147 148 /** Returns status for credential manager UI */ getStatus()149 public UiStatus getStatus() { 150 return mStatus; 151 } 152 153 /** 154 * Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI, 155 * by the calling app process. The bottom-sheet navigates to the default page when the intent 156 * is invoked. 157 * 158 * @param requestInfo the information about the request 159 * @param providerDataList the list of provider data from remote providers 160 */ createPendingIntent( RequestInfo requestInfo, ArrayList<ProviderData> providerDataList, RequestSessionMetric requestSessionMetric)161 public PendingIntent createPendingIntent( 162 RequestInfo requestInfo, ArrayList<ProviderData> providerDataList, 163 RequestSessionMetric requestSessionMetric) { 164 List<CredentialProviderInfo> allProviders = 165 CredentialProviderInfoFactory.getCredentialProviderServices( 166 mContext, 167 mUserId, 168 CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY, 169 mEnabledProviders, 170 // Don't need primary providers here. 171 new HashSet<ComponentName>()); 172 173 List<DisabledProviderData> disabledProviderDataList = allProviders.stream() 174 .filter(provider -> !provider.isEnabled()) 175 .map(disabledProvider -> new DisabledProviderData( 176 disabledProvider.getComponentName().flattenToString())).toList(); 177 178 IntentCreationResult intentCreationResult = IntentFactory 179 .createCredentialSelectorIntentForCredMan(mContext, requestInfo, providerDataList, 180 new ArrayList<>(disabledProviderDataList), mResultReceiver, mUserId); 181 requestSessionMetric.collectUiConfigurationResults( 182 mContext, intentCreationResult, mUserId); 183 Intent intent = intentCreationResult.getIntent(); 184 intent.setAction(UUID.randomUUID().toString()); 185 if (Flags.frameworkSessionIdMetricBundle()) { 186 intent.putExtra(SESSION_ID_TRACK_ONE, 187 requestSessionMetric.getInitialPhaseMetric().getSessionIdCaller()); 188 intent.putExtra(SESSION_ID_TRACK_TWO, requestSessionMetric.getSessionIdTrackTwo()); 189 } 190 //TODO: Create unique pending intent using request code and cancel any pre-existing pending 191 // intents 192 return PendingIntent.getActivityAsUser( 193 mContext, /*requestCode=*/0, intent, 194 PendingIntent.FLAG_MUTABLE, /*options=*/null, 195 UserHandle.of(mUserId)); 196 } 197 198 /** 199 * Creates an {@link Intent} to be used to invoke the credential manager selector UI, 200 * by the calling app process. This intent is invoked from the Autofill flow, when the user 201 * requests to bring up the 'All Options' page of the credential bottom-sheet. When the user 202 * clicks on the pinned entry, the intent will bring up the 'All Options' page of the 203 * bottom-sheet. The provider data list is processed by the credential autofill service for 204 * each autofill id and passed in as extras in the pending intent set as authentication 205 * of the pinned entry. 206 * 207 * @param requestInfo the information about the request 208 * @param requestSessionMetric the metric object for logging 209 */ createIntentForAutofill(RequestInfo requestInfo, RequestSessionMetric requestSessionMetric)210 public Intent createIntentForAutofill(RequestInfo requestInfo, 211 RequestSessionMetric requestSessionMetric) { 212 IntentCreationResult intentCreationResult = IntentFactory 213 .createCredentialSelectorIntentForAutofill(mContext, requestInfo, new ArrayList<>(), 214 mResultReceiver, mUserId); 215 requestSessionMetric.collectUiConfigurationResults( 216 mContext, intentCreationResult, mUserId); 217 return intentCreationResult.getIntent(); 218 } 219 } 220