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.ui.DisabledProviderData; 26 import android.credentials.ui.IntentFactory; 27 import android.credentials.ui.ProviderData; 28 import android.credentials.ui.RequestInfo; 29 import android.credentials.ui.UserSelectionDialogResult; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.IBinder; 33 import android.os.Looper; 34 import android.os.ResultReceiver; 35 import android.os.UserHandle; 36 import android.service.credentials.CredentialProviderInfoFactory; 37 import android.util.Slog; 38 39 import java.util.ArrayList; 40 import java.util.HashSet; 41 import java.util.List; 42 import java.util.Set; 43 import java.util.UUID; 44 45 /** Initiates the Credential Manager UI and receives results. */ 46 public class CredentialManagerUi { 47 private static final String TAG = "CredentialManagerUi"; 48 @NonNull 49 private final CredentialManagerUiCallback mCallbacks; 50 @NonNull 51 private final Context mContext; 52 53 private final int mUserId; 54 55 private UiStatus mStatus; 56 57 private final Set<ComponentName> mEnabledProviders; 58 59 enum UiStatus { 60 IN_PROGRESS, 61 USER_INTERACTION, 62 NOT_STARTED, TERMINATED 63 } 64 65 @NonNull 66 private final ResultReceiver mResultReceiver = new ResultReceiver( 67 new Handler(Looper.getMainLooper())) { 68 @Override 69 protected void onReceiveResult(int resultCode, Bundle resultData) { 70 handleUiResult(resultCode, resultData); 71 } 72 }; 73 handleUiResult(int resultCode, Bundle resultData)74 private void handleUiResult(int resultCode, Bundle resultData) { 75 76 switch (resultCode) { 77 case UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION: 78 mStatus = UiStatus.IN_PROGRESS; 79 UserSelectionDialogResult selection = UserSelectionDialogResult 80 .fromResultData(resultData); 81 if (selection != null) { 82 mCallbacks.onUiSelection(selection); 83 } else { 84 Slog.i(TAG, "No selection found in UI result"); 85 } 86 break; 87 case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED: 88 89 mStatus = UiStatus.TERMINATED; 90 mCallbacks.onUiCancellation(/* isUserCancellation= */ true); 91 break; 92 case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS: 93 94 mStatus = UiStatus.TERMINATED; 95 mCallbacks.onUiCancellation(/* isUserCancellation= */ false); 96 break; 97 case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE: 98 mStatus = UiStatus.TERMINATED; 99 mCallbacks.onUiSelectorInvocationFailure(); 100 break; 101 default: 102 mStatus = UiStatus.IN_PROGRESS; 103 mCallbacks.onUiSelectorInvocationFailure(); 104 break; 105 } 106 } 107 108 /** Creates intent that is ot be invoked to cancel an in-progress UI session. */ createCancelIntent(IBinder requestId, String packageName)109 public Intent createCancelIntent(IBinder requestId, String packageName) { 110 return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true, 111 packageName); 112 } 113 114 /** 115 * Interface to be implemented by any class that wishes to get callbacks from the UI. 116 */ 117 public interface CredentialManagerUiCallback { 118 /** Called when the user makes a selection. */ onUiSelection(UserSelectionDialogResult selection)119 void onUiSelection(UserSelectionDialogResult selection); 120 121 /** Called when the UI is canceled without a successful provider result. */ onUiCancellation(boolean isUserCancellation)122 void onUiCancellation(boolean isUserCancellation); 123 124 /** Called when the selector UI fails to come up (mostly due to parsing issue today). */ onUiSelectorInvocationFailure()125 void onUiSelectorInvocationFailure(); 126 } 127 CredentialManagerUi(Context context, int userId, CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders)128 public CredentialManagerUi(Context context, int userId, 129 CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders) { 130 mContext = context; 131 mUserId = userId; 132 mCallbacks = callbacks; 133 mEnabledProviders = enabledProviders; 134 mStatus = UiStatus.IN_PROGRESS; 135 } 136 137 /** Set status for credential manager UI */ setStatus(UiStatus status)138 public void setStatus(UiStatus status) { 139 mStatus = status; 140 } 141 142 /** Returns status for credential manager UI */ getStatus()143 public UiStatus getStatus() { 144 return mStatus; 145 } 146 147 /** 148 * Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI, 149 * by the calling app process. 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)154 public PendingIntent createPendingIntent( 155 RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) { 156 List<CredentialProviderInfo> allProviders = 157 CredentialProviderInfoFactory.getCredentialProviderServices( 158 mContext, 159 mUserId, 160 CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY, 161 mEnabledProviders, 162 // Don't need primary providers here. 163 new HashSet<ComponentName>()); 164 165 List<DisabledProviderData> disabledProviderDataList = allProviders.stream() 166 .filter(provider -> !provider.isEnabled()) 167 .map(disabledProvider -> new DisabledProviderData( 168 disabledProvider.getComponentName().flattenToString())).toList(); 169 170 Intent intent = IntentFactory.createCredentialSelectorIntent(requestInfo, providerDataList, 171 new ArrayList<>(disabledProviderDataList), mResultReceiver) 172 .setAction(UUID.randomUUID().toString()); 173 //TODO: Create unique pending intent using request code and cancel any pre-existing pending 174 // intents 175 return PendingIntent.getActivityAsUser( 176 mContext, /*requestCode=*/0, intent, 177 PendingIntent.FLAG_IMMUTABLE, /*options=*/null, 178 UserHandle.of(mUserId)); 179 } 180 } 181