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