From ea03b3e2a07742d80365ea3c8482e2ffb7a6ded3 Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Thu, 27 Apr 2023 21:27:52 +0000 Subject: [PATCH] Add combined provider class that manages new settings UI Example of how to use: https://paste.googleplex.com/6523798525313024 Test: make Bug: 278919696 Change-Id: I934a5f6d02b50f8c97bda6d997902a42ff88e26f --- .../credentials/CombinedProviderInfo.java | 262 ++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 src/com/android/settings/applications/credentials/CombinedProviderInfo.java diff --git a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java new file mode 100644 index 00000000000..ce985975c90 --- /dev/null +++ b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java @@ -0,0 +1,262 @@ +/* + * 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; + } +}