Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ea03b3e2 authored by Becca Hughes's avatar Becca Hughes
Browse files

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
parent 83c04b32
Loading
Loading
Loading
Loading
+262 −0
Original line number Diff line number Diff line
/*
 * 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<CredentialProviderInfo> 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<CredentialProviderInfo> 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<CredentialProviderInfo> 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<String, ServiceInfo> flattenedNamesToServices = new HashMap<>();
        List<String> 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<String> 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<CombinedProviderInfo> 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<CombinedProviderInfo> buildMergedList(
            List<AutofillServiceInfo> asiList,
            List<CredentialProviderInfo> cpiList,
            @Nullable String defaultAutofillProvider) {
        ComponentName defaultAutofillProviderComponent =
                (defaultAutofillProvider == null)
                        ? null
                        : ComponentName.unflattenFromString(defaultAutofillProvider);

        // Index the autofill providers by package name.
        Set<String> packageNames = new HashSet<>();
        Map<String, List<AutofillServiceInfo>> 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<String, List<CredentialProviderInfo>> 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<CombinedProviderInfo> cmpi = new ArrayList<>();
        for (String packageName : packageNames) {
            List<AutofillServiceInfo> asi = autofillServices.get(packageName);
            List<CredentialProviderInfo> 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;
    }
}