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