1 /* 2 * Copyright 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 android.credentials.selection; 18 19 import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED; 20 import static android.credentials.flags.Flags.configurableSelectorUiEnabled; 21 22 import android.annotation.FlaggedApi; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.SuppressLint; 26 import android.annotation.TestApi; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.pm.ActivityInfo; 31 import android.content.pm.PackageManager; 32 import android.content.res.Resources; 33 import android.os.IBinder; 34 import android.os.Parcel; 35 import android.os.ResultReceiver; 36 import android.text.TextUtils; 37 import android.util.Slog; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 41 import java.util.ArrayList; 42 43 /** 44 * Helpers for generating the intents and related extras parameters to launch the UI activities. 45 * 46 * @hide 47 */ 48 @TestApi 49 @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED) 50 public class IntentFactory { 51 52 /** 53 * Generate a new launch intent to the Credential Selector UI for auto-filling. This intent is 54 * invoked from the Autofill flow, when the user requests to bring up the 'All Options' page of 55 * the credential bottom-sheet. When the user clicks on the pinned entry, the intent will bring 56 * up the 'All Options' page of the bottom-sheet. The provider data list is processed by the 57 * credential autofill service for each autofill id and passed in as an auth extra. 58 * 59 * @hide 60 */ 61 @NonNull createCredentialSelectorIntentForAutofill( @onNull Context context, @NonNull RequestInfo requestInfo, @SuppressLint("ConcreteCollection") @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, @NonNull ResultReceiver resultReceiver)62 public static IntentCreationResult createCredentialSelectorIntentForAutofill( 63 @NonNull Context context, 64 @NonNull RequestInfo requestInfo, 65 @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling. 66 @NonNull 67 ArrayList<DisabledProviderData> disabledProviderDataList, 68 @NonNull ResultReceiver resultReceiver) { 69 return createCredentialSelectorIntentInternal(context, requestInfo, 70 disabledProviderDataList, resultReceiver); 71 } 72 73 /** 74 * Generate a new launch intent to the Credential Selector UI. 75 * 76 * @param context the CredentialManager system service (only expected caller) 77 * context that may be used to query existence of the key UI 78 * application 79 * @param disabledProviderDataList the list of disabled provider data that when non-empty the 80 * UI should accordingly generate an entry suggesting the user 81 * to navigate to settings and enable them 82 * @param enabledProviderDataList the list of enabled provider that contain options for this 83 * request; the UI should render each option to the user for 84 * selection 85 * @param requestInfo the display information about the given app request 86 * @param resultReceiver used by the UI to send the UI selection result back 87 * @hide 88 */ 89 @NonNull createCredentialSelectorIntentForCredMan( @onNull Context context, @NonNull RequestInfo requestInfo, @SuppressLint("ConcreteCollection") @NonNull ArrayList<ProviderData> enabledProviderDataList, @SuppressLint("ConcreteCollection") @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, @NonNull ResultReceiver resultReceiver)90 public static IntentCreationResult createCredentialSelectorIntentForCredMan( 91 @NonNull Context context, 92 @NonNull RequestInfo requestInfo, 93 @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling. 94 @NonNull 95 ArrayList<ProviderData> enabledProviderDataList, 96 @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling. 97 @NonNull 98 ArrayList<DisabledProviderData> disabledProviderDataList, 99 @NonNull ResultReceiver resultReceiver) { 100 IntentCreationResult result = createCredentialSelectorIntentInternal(context, requestInfo, 101 disabledProviderDataList, resultReceiver); 102 result.getIntent().putParcelableArrayListExtra( 103 ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList); 104 return result; 105 } 106 107 /** 108 * Generate a new launch intent to the Credential Selector UI. 109 * 110 * @param context the CredentialManager system service (only expected caller) 111 * context that may be used to query existence of the key UI 112 * application 113 * @param disabledProviderDataList the list of disabled provider data that when non-empty the 114 * UI should accordingly generate an entry suggesting the user 115 * to navigate to settings and enable them 116 * @param enabledProviderDataList the list of enabled provider that contain options for this 117 * request; the UI should render each option to the user for 118 * selection 119 * @param requestInfo the display information about the given app request 120 * @param resultReceiver used by the UI to send the UI selection result back 121 */ 122 @VisibleForTesting 123 @NonNull createCredentialSelectorIntent( @onNull Context context, @NonNull RequestInfo requestInfo, @SuppressLint("ConcreteCollection") @NonNull ArrayList<ProviderData> enabledProviderDataList, @SuppressLint("ConcreteCollection") @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, @NonNull ResultReceiver resultReceiver)124 public static Intent createCredentialSelectorIntent( 125 @NonNull Context context, 126 @NonNull RequestInfo requestInfo, 127 @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling. 128 @NonNull 129 ArrayList<ProviderData> enabledProviderDataList, 130 @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling. 131 @NonNull 132 ArrayList<DisabledProviderData> disabledProviderDataList, 133 @NonNull ResultReceiver resultReceiver) { 134 return createCredentialSelectorIntentForCredMan(context, requestInfo, 135 enabledProviderDataList, disabledProviderDataList, resultReceiver).getIntent(); 136 } 137 138 /** 139 * Creates an Intent that cancels any UI matching the given request token id. 140 */ 141 @VisibleForTesting 142 @NonNull createCancelUiIntent(@onNull Context context, @NonNull IBinder requestToken, boolean shouldShowCancellationUi, @NonNull String appPackageName)143 public static Intent createCancelUiIntent(@NonNull Context context, 144 @NonNull IBinder requestToken, boolean shouldShowCancellationUi, 145 @NonNull String appPackageName) { 146 Intent intent = new Intent(); 147 IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent); 148 setCredentialSelectorUiComponentName(context, intent, intentResultBuilder); 149 intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST, 150 new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi, 151 appPackageName)); 152 return intent; 153 } 154 155 /** 156 * Generate a new launch intent to the Credential Selector UI. 157 */ 158 @NonNull createCredentialSelectorIntentInternal( @onNull Context context, @NonNull RequestInfo requestInfo, @SuppressLint("ConcreteCollection") @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, @NonNull ResultReceiver resultReceiver)159 private static IntentCreationResult createCredentialSelectorIntentInternal( 160 @NonNull Context context, 161 @NonNull RequestInfo requestInfo, 162 @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling. 163 @NonNull 164 ArrayList<DisabledProviderData> disabledProviderDataList, 165 @NonNull ResultReceiver resultReceiver) { 166 Intent intent = new Intent(); 167 IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent); 168 setCredentialSelectorUiComponentName(context, intent, intentResultBuilder); 169 intent.putParcelableArrayListExtra( 170 ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList); 171 intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo); 172 intent.putExtra( 173 Constants.EXTRA_RESULT_RECEIVER, toIpcFriendlyResultReceiver(resultReceiver)); 174 return intentResultBuilder.build(); 175 } 176 setCredentialSelectorUiComponentName(@onNull Context context, @NonNull Intent intent, @NonNull IntentCreationResult.Builder intentResultBuilder)177 private static void setCredentialSelectorUiComponentName(@NonNull Context context, 178 @NonNull Intent intent, @NonNull IntentCreationResult.Builder intentResultBuilder) { 179 if (configurableSelectorUiEnabled()) { 180 ComponentName componentName = getOemOverrideComponentName(context, intentResultBuilder); 181 182 ComponentName fallbackUiComponentName = null; 183 try { 184 fallbackUiComponentName = ComponentName.unflattenFromString( 185 Resources.getSystem().getString( 186 com.android.internal.R.string 187 .config_fallbackCredentialManagerDialogComponent)); 188 intentResultBuilder.setFallbackUiPackageName( 189 fallbackUiComponentName.getPackageName()); 190 } catch (Exception e) { 191 Slog.w(TAG, "Fallback CredMan IU not found: " + e); 192 } 193 194 if (componentName == null) { 195 componentName = fallbackUiComponentName; 196 } 197 198 intent.setComponent(componentName); 199 } else { 200 ComponentName componentName = ComponentName.unflattenFromString(Resources.getSystem() 201 .getString(com.android.internal.R.string 202 .config_fallbackCredentialManagerDialogComponent)); 203 intent.setComponent(componentName); 204 } 205 } 206 207 /** 208 * Returns null if there is not an enabled and valid oem override component. It means the 209 * default platform UI component name should be used instead. 210 */ 211 @Nullable getOemOverrideComponentName(@onNull Context context, @NonNull IntentCreationResult.Builder intentResultBuilder)212 private static ComponentName getOemOverrideComponentName(@NonNull Context context, 213 @NonNull IntentCreationResult.Builder intentResultBuilder) { 214 ComponentName result = null; 215 String oemComponentString = 216 Resources.getSystem() 217 .getString( 218 com.android.internal.R.string 219 .config_oemCredentialManagerDialogComponent); 220 if (!TextUtils.isEmpty(oemComponentString)) { 221 ComponentName oemComponentName = null; 222 try { 223 oemComponentName = ComponentName.unflattenFromString( 224 oemComponentString); 225 } catch (Exception e) { 226 Slog.i(TAG, "Failed to parse OEM component name " + oemComponentString + ": " + e); 227 } 228 if (oemComponentName != null) { 229 try { 230 intentResultBuilder.setOemUiPackageName(oemComponentName.getPackageName()); 231 ActivityInfo info = context.getPackageManager().getActivityInfo( 232 oemComponentName, 233 PackageManager.ComponentInfoFlags.of( 234 PackageManager.MATCH_SYSTEM_ONLY)); 235 boolean oemComponentEnabled = info.enabled; 236 int runtimeComponentEnabledState = context.getPackageManager() 237 .getComponentEnabledSetting(oemComponentName); 238 if (runtimeComponentEnabledState == PackageManager 239 .COMPONENT_ENABLED_STATE_ENABLED) { 240 oemComponentEnabled = true; 241 } else if (runtimeComponentEnabledState == PackageManager 242 .COMPONENT_ENABLED_STATE_DISABLED) { 243 oemComponentEnabled = false; 244 } 245 if (oemComponentEnabled && info.exported) { 246 intentResultBuilder.setOemUiUsageStatus(IntentCreationResult 247 .OemUiUsageStatus.SUCCESS); 248 Slog.i(TAG, 249 "Found enabled oem CredMan UI component." 250 + oemComponentString); 251 result = oemComponentName; 252 } else { 253 intentResultBuilder.setOemUiUsageStatus(IntentCreationResult 254 .OemUiUsageStatus.OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED); 255 Slog.i(TAG, 256 "Found enabled oem CredMan UI component but it was not " 257 + "enabled."); 258 } 259 } catch (PackageManager.NameNotFoundException e) { 260 intentResultBuilder.setOemUiUsageStatus(IntentCreationResult.OemUiUsageStatus 261 .OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND); 262 Slog.i(TAG, "Unable to find oem CredMan UI component: " 263 + oemComponentString + "."); 264 } 265 } else { 266 intentResultBuilder.setOemUiUsageStatus(IntentCreationResult.OemUiUsageStatus 267 .OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND); 268 Slog.i(TAG, "Invalid OEM ComponentName format."); 269 } 270 } else { 271 intentResultBuilder.setOemUiUsageStatus( 272 IntentCreationResult.OemUiUsageStatus.OEM_UI_CONFIG_NOT_SPECIFIED); 273 Slog.i(TAG, "Invalid empty OEM component name."); 274 } 275 return result; 276 } 277 278 /** 279 * Convert an instance of a "locally-defined" ResultReceiver to an instance of {@link 280 * android.os.ResultReceiver} itself, which the receiving process will be able to unmarshall. 281 */ toIpcFriendlyResultReceiver( T resultReceiver)282 private static <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver( 283 T resultReceiver) { 284 final Parcel parcel = Parcel.obtain(); 285 resultReceiver.writeToParcel(parcel, 0); 286 parcel.setDataPosition(0); 287 288 final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel); 289 parcel.recycle(); 290 291 return ipcFriendly; 292 } 293 IntentFactory()294 private IntentFactory() { 295 } 296 297 private static final String TAG = "CredManIntentHelper"; 298 } 299