1 /* 2 * Copyright (C) 2023 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 com.android.settings.applications.credentials; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.ServiceInfo; 25 import android.credentials.CredentialProviderInfo; 26 import android.graphics.drawable.Drawable; 27 import android.service.autofill.AutofillServiceInfo; 28 import android.text.TextUtils; 29 import android.util.IconDrawableFactory; 30 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.HashMap; 34 import java.util.HashSet; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Set; 38 39 /** 40 * Holds combined autofill and credential manager data grouped by package name. Contains backing 41 * logic for each row in settings. 42 */ 43 public final class CombinedProviderInfo { 44 private final List<CredentialProviderInfo> mCredentialProviderInfos; 45 private final @Nullable AutofillServiceInfo mAutofillServiceInfo; 46 private final boolean mIsDefaultAutofillProvider; 47 private final boolean mIsPrimaryCredmanProvider; 48 49 /** Constructs an information instance from both autofill and credential provider. */ CombinedProviderInfo( @ullable List<CredentialProviderInfo> cpis, @Nullable AutofillServiceInfo asi, boolean isDefaultAutofillProvider, boolean isPrimaryCredmanProvider)50 public CombinedProviderInfo( 51 @Nullable List<CredentialProviderInfo> cpis, 52 @Nullable AutofillServiceInfo asi, 53 boolean isDefaultAutofillProvider, 54 boolean isPrimaryCredmanProvider) { 55 if (cpis == null) { 56 mCredentialProviderInfos = new ArrayList<>(); 57 } else { 58 mCredentialProviderInfos = new ArrayList<>(cpis); 59 } 60 mAutofillServiceInfo = asi; 61 mIsDefaultAutofillProvider = isDefaultAutofillProvider; 62 mIsPrimaryCredmanProvider = isPrimaryCredmanProvider; 63 } 64 65 /** Returns the credential provider info. */ 66 @NonNull getCredentialProviderInfos()67 public List<CredentialProviderInfo> getCredentialProviderInfos() { 68 return mCredentialProviderInfos; 69 } 70 71 /** Returns the autofill provider info. */ 72 @Nullable getAutofillServiceInfo()73 public AutofillServiceInfo getAutofillServiceInfo() { 74 return mAutofillServiceInfo; 75 } 76 77 /** Returns the application info. */ getApplicationInfo()78 public @Nullable ApplicationInfo getApplicationInfo() { 79 if (!mCredentialProviderInfos.isEmpty()) { 80 return mCredentialProviderInfos.get(0).getServiceInfo().applicationInfo; 81 } 82 return mAutofillServiceInfo.getServiceInfo().applicationInfo; 83 } 84 85 /** Returns the app icon. */ 86 @Nullable getAppIcon(@onNull Context context, int userId)87 public Drawable getAppIcon(@NonNull Context context, int userId) { 88 final IconDrawableFactory factory = IconDrawableFactory.newInstance(context); 89 final ServiceInfo brandingService = getBrandingService(); 90 final ApplicationInfo appInfo = getApplicationInfo(); 91 92 Drawable icon = null; 93 if (brandingService != null && appInfo != null) { 94 icon = factory.getBadgedIcon(brandingService, appInfo, userId); 95 } 96 97 // If the branding service gave us a icon then use that. 98 if (icon != null) { 99 return icon; 100 } 101 102 // Otherwise fallback to the app icon and then the package name. 103 if (appInfo != null) { 104 return factory.getBadgedIcon(appInfo, userId); 105 } 106 return null; 107 } 108 109 /** Returns the app name. */ 110 @Nullable getAppName(@onNull Context context)111 public CharSequence getAppName(@NonNull Context context) { 112 CharSequence name = ""; 113 ServiceInfo brandingService = getBrandingService(); 114 if (brandingService != null) { 115 name = brandingService.loadLabel(context.getPackageManager()); 116 } 117 118 // If the branding service gave us a name then use that. 119 if (!TextUtils.isEmpty(name)) { 120 return name; 121 } 122 123 // Otherwise fallback to the app label and then the package name. 124 final ApplicationInfo appInfo = getApplicationInfo(); 125 if (appInfo != null) { 126 name = appInfo.loadLabel(context.getPackageManager()); 127 if (TextUtils.isEmpty(name)) { 128 return appInfo.packageName; 129 } 130 } 131 return ""; 132 } 133 134 /** Gets the service to use for branding (name, icons). */ getBrandingService()135 public @Nullable ServiceInfo getBrandingService() { 136 // If the app has an autofill service then use that. 137 if (mAutofillServiceInfo != null) { 138 return mAutofillServiceInfo.getServiceInfo(); 139 } 140 141 // If there are no credman providers then stop here. 142 if (mCredentialProviderInfos.isEmpty()) { 143 return null; 144 } 145 146 // Build a list of credential providers and sort them by component names 147 // alphabetically to ensure we are deterministic when picking the provider. 148 Map<String, ServiceInfo> flattenedNamesToServices = new HashMap<>(); 149 List<String> flattenedNames = new ArrayList<>(); 150 for (CredentialProviderInfo cpi : mCredentialProviderInfos) { 151 final String flattenedName = cpi.getComponentName().flattenToString(); 152 flattenedNamesToServices.put(flattenedName, cpi.getServiceInfo()); 153 flattenedNames.add(flattenedName); 154 } 155 156 Collections.sort(flattenedNames); 157 return flattenedNamesToServices.get(flattenedNames.get(0)); 158 } 159 160 /** Returns whether the provider is the default autofill provider. */ isDefaultAutofillProvider()161 public boolean isDefaultAutofillProvider() { 162 return mIsDefaultAutofillProvider; 163 } 164 165 /** Returns whether the provider is the default credman provider. */ isPrimaryCredmanProvider()166 public boolean isPrimaryCredmanProvider() { 167 return mIsPrimaryCredmanProvider; 168 } 169 170 /** Returns the settings subtitle. */ 171 @Nullable getSettingsSubtitle()172 public String getSettingsSubtitle() { 173 List<String> subtitles = new ArrayList<>(); 174 for (CredentialProviderInfo cpi : mCredentialProviderInfos) { 175 // Convert from a CharSequence. 176 String subtitle = String.valueOf(cpi.getSettingsSubtitle()); 177 if (subtitle != null && !TextUtils.isEmpty(subtitle) && !subtitle.equals("null")) { 178 subtitles.add(subtitle); 179 } 180 } 181 182 if (subtitles.size() == 0) { 183 return ""; 184 } 185 186 return String.join(", ", subtitles); 187 } 188 189 /** Returns the autofill component name string. */ 190 @Nullable getAutofillServiceString()191 public String getAutofillServiceString() { 192 if (mAutofillServiceInfo != null) { 193 return mAutofillServiceInfo.getServiceInfo().getComponentName().flattenToString(); 194 } 195 return null; 196 } 197 198 /** Returns the provider that gets the top spot. */ getTopProvider( List<CombinedProviderInfo> providers)199 public static @Nullable CombinedProviderInfo getTopProvider( 200 List<CombinedProviderInfo> providers) { 201 // If there is an autofill provider then it should be the 202 // top app provider. 203 for (CombinedProviderInfo cpi : providers) { 204 if (cpi.isDefaultAutofillProvider()) { 205 return cpi; 206 } 207 } 208 209 // If there is a primary cred man provider then return that. 210 for (CombinedProviderInfo cpi : providers) { 211 if (cpi.isPrimaryCredmanProvider()) { 212 return cpi; 213 } 214 } 215 216 return null; 217 } 218 buildMergedList( List<AutofillServiceInfo> asiList, List<CredentialProviderInfo> cpiList, @Nullable String defaultAutofillProvider)219 public static List<CombinedProviderInfo> buildMergedList( 220 List<AutofillServiceInfo> asiList, 221 List<CredentialProviderInfo> cpiList, 222 @Nullable String defaultAutofillProvider) { 223 ComponentName defaultAutofillProviderComponent = 224 (defaultAutofillProvider == null) 225 ? null 226 : ComponentName.unflattenFromString(defaultAutofillProvider); 227 228 // Index the autofill providers by package name. 229 Set<String> packageNames = new HashSet<>(); 230 Map<String, List<AutofillServiceInfo>> autofillServices = new HashMap<>(); 231 for (AutofillServiceInfo asi : asiList) { 232 final String packageName = asi.getServiceInfo().packageName; 233 if (!autofillServices.containsKey(packageName)) { 234 autofillServices.put(packageName, new ArrayList<>()); 235 } 236 237 autofillServices.get(packageName).add(asi); 238 packageNames.add(packageName); 239 } 240 241 // Index the credman providers by package name. 242 Map<String, List<CredentialProviderInfo>> credmanServices = new HashMap<>(); 243 for (CredentialProviderInfo cpi : cpiList) { 244 String packageName = cpi.getServiceInfo().packageName; 245 if (!credmanServices.containsKey(packageName)) { 246 credmanServices.put(packageName, new ArrayList<>()); 247 } 248 249 credmanServices.get(packageName).add(cpi); 250 packageNames.add(packageName); 251 } 252 253 // Now go through and build the joint datasets. 254 List<CombinedProviderInfo> cmpi = new ArrayList<>(); 255 for (String packageName : packageNames) { 256 List<AutofillServiceInfo> asi = 257 autofillServices.getOrDefault(packageName, new ArrayList<>()); 258 List<CredentialProviderInfo> cpi = 259 credmanServices.getOrDefault(packageName, new ArrayList<>()); 260 261 // If there are multiple autofill services then pick the first one. 262 AutofillServiceInfo selectedAsi = null; 263 if (asi != null && !asi.isEmpty()) { 264 selectedAsi = asi.get(0); 265 } 266 267 // Check if we are the default autofill provider. 268 boolean isDefaultAutofillProvider = false; 269 if (defaultAutofillProviderComponent != null 270 && defaultAutofillProviderComponent.getPackageName().equals(packageName)) { 271 isDefaultAutofillProvider = true; 272 } 273 274 // Check if we have any enabled cred man services. 275 boolean isPrimaryCredmanProvider = false; 276 if (cpi != null && !cpi.isEmpty()) { 277 isPrimaryCredmanProvider = cpi.get(0).isPrimary(); 278 } 279 280 cmpi.add( 281 new CombinedProviderInfo( 282 cpi, selectedAsi, isDefaultAutofillProvider, isPrimaryCredmanProvider)); 283 } 284 285 return cmpi; 286 } 287 } 288