/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.applications.credentials; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; import android.credentials.CredentialProviderInfo; import android.graphics.drawable.Drawable; import android.service.autofill.AutofillServiceInfo; import android.text.TextUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Holds combined autofill and credential manager data grouped by package name. Contains backing * logic for each row in settings. */ public final class CombinedProviderInfo { private final List mCredentialProviderInfos; private final @Nullable AutofillServiceInfo mAutofillServiceInfo; private final boolean mIsDefaultAutofillProvider; private final boolean mIsDefaultCredmanProvider; /** Constructs an information instance from both autofill and credential provider. */ public CombinedProviderInfo( @Nullable List cpis, @Nullable AutofillServiceInfo asi, boolean isDefaultAutofillProvider, boolean isDefaultCredmanProvider) { mCredentialProviderInfos = new ArrayList<>(cpis); mAutofillServiceInfo = asi; mIsDefaultAutofillProvider = isDefaultAutofillProvider; mIsDefaultCredmanProvider = isDefaultCredmanProvider; } /** Returns the credential provider info. */ @Nullable public List getCredentialProviderInfos() { return mCredentialProviderInfos; } /** Returns the autofill provider info. */ @Nullable public AutofillServiceInfo getAutofillServiceInfo() { return mAutofillServiceInfo; } /** Returns the application info. */ public @Nullable ApplicationInfo getApplicationInfo() { if (!mCredentialProviderInfos.isEmpty()) { return mCredentialProviderInfos.get(0).getServiceInfo().applicationInfo; } return mAutofillServiceInfo.getServiceInfo().applicationInfo; } /** Returns the app icon. */ @Nullable public Drawable getAppIcon(@NonNull Context context) { Drawable icon = null; ServiceInfo brandingService = getBrandingService(); if (brandingService != null) { icon = brandingService.loadIcon(context.getPackageManager()); } // If the branding service gave us a icon then use that. if (icon != null) { return icon; } // Otherwise fallback to the app label and then the package name. return getApplicationInfo().loadIcon(context.getPackageManager()); } /** Returns the app name. */ @Nullable public CharSequence getAppName(@NonNull Context context) { CharSequence name = ""; ServiceInfo brandingService = getBrandingService(); if (brandingService != null) { name = brandingService.loadLabel(context.getPackageManager()); } // If the branding service gave us a name then use that. if (!TextUtils.isEmpty(name)) { return name; } // Otherwise fallback to the app label and then the package name. name = getApplicationInfo().loadLabel(context.getPackageManager()); if (TextUtils.isEmpty(name)) { name = getApplicationInfo().packageName; } return name; } /** Gets the service to use for branding (name, icons). */ public @Nullable ServiceInfo getBrandingService() { // If the app has an autofill service then use that. if (mAutofillServiceInfo != null) { return mAutofillServiceInfo.getServiceInfo(); } // If there are no credman providers then stop here. if (mCredentialProviderInfos.isEmpty()) { return null; } // Build a list of credential providers and sort them by component names // alphabetically to ensure we are deterministic when picking the provider. Map flattenedNamesToServices = new HashMap<>(); List flattenedNames = new ArrayList<>(); for (CredentialProviderInfo cpi : mCredentialProviderInfos) { final String flattenedName = cpi.getComponentName().flattenToString(); flattenedNamesToServices.put(flattenedName, cpi.getServiceInfo()); flattenedNames.add(flattenedName); } Collections.sort(flattenedNames); return flattenedNamesToServices.get(flattenedNames.get(0)); } /** Returns whether the provider is the default autofill provider. */ public boolean isDefaultAutofillProvider() { return mIsDefaultAutofillProvider; } /** Returns whether the provider is the default credman provider. */ public boolean isDefaultCredmanProvider() { return mIsDefaultCredmanProvider; } /** Returns the settings subtitle. */ @Nullable public String getSettingsSubtitle() { List subtitles = new ArrayList<>(); for (CredentialProviderInfo cpi : mCredentialProviderInfos) { // Convert from a CharSequence. String subtitle = String.valueOf(cpi.getSettingsSubtitle()); if (subtitle != null && !TextUtils.isEmpty(subtitle) && !subtitle.equals("null")) { subtitles.add(subtitle); } } if (subtitles.size() == 0) { return ""; } return String.join(", ", subtitles); } /** Returns the autofill component name string. */ @Nullable public String getAutofillServiceString() { if (mAutofillServiceInfo != null) { return mAutofillServiceInfo.getServiceInfo().getComponentName().flattenToString(); } return null; } /** Returns the provider that gets the top spot. */ public static @Nullable CombinedProviderInfo getTopProvider( List providers) { // If there is an autofill provider then it should be the // top app provider. for (CombinedProviderInfo cpi : providers) { if (cpi.isDefaultAutofillProvider()) { return cpi; } } // TODO(280454916): Add logic here. return null; } public static List buildMergedList( List asiList, List cpiList, @Nullable String defaultAutofillProvider) { ComponentName defaultAutofillProviderComponent = (defaultAutofillProvider == null) ? null : ComponentName.unflattenFromString(defaultAutofillProvider); // Index the autofill providers by package name. Set packageNames = new HashSet<>(); Map> autofillServices = new HashMap<>(); for (AutofillServiceInfo asi : asiList) { final String packageName = asi.getServiceInfo().packageName; if (!autofillServices.containsKey(packageName)) { autofillServices.put(packageName, new ArrayList<>()); } autofillServices.get(packageName).add(asi); packageNames.add(packageName); } // Index the credman providers by package name. Map> credmanServices = new HashMap<>(); for (CredentialProviderInfo cpi : cpiList) { String packageName = cpi.getServiceInfo().packageName; if (!credmanServices.containsKey(packageName)) { credmanServices.put(packageName, new ArrayList<>()); } credmanServices.get(packageName).add(cpi); packageNames.add(packageName); } // Now go through and build the joint datasets. List cmpi = new ArrayList<>(); for (String packageName : packageNames) { List asi = autofillServices.get(packageName); List cpi = credmanServices.get(packageName); // If there are multiple autofill services then pick the first one. AutofillServiceInfo selectedAsi = asi.isEmpty() ? null : asi.get(0); // Check if we are the default autofill provider. boolean isDefaultAutofillProvider = false; if (defaultAutofillProviderComponent != null && defaultAutofillProviderComponent.getPackageName().equals(packageName)) { isDefaultAutofillProvider = true; } // Check if we have any enabled cred man services. boolean isDefaultCredmanProvider = false; if (!cpi.isEmpty()) { isDefaultCredmanProvider = cpi.get(0).isEnabled(); } cmpi.add( new CombinedProviderInfo( cpi, selectedAsi, isDefaultAutofillProvider, isDefaultCredmanProvider)); } return cmpi; } }