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

Commit 58cc8182 authored by Becca Hughes's avatar Becca Hughes Committed by Automerger Merge Worker
Browse files

Merge "Add combined provider class that manages new settings UI" into udc-dev...

Merge "Add combined provider class that manages new settings UI" into udc-dev am: 99f45dd5 am: c5f2edcb am: 1ac2d51e

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/22911585



Change-Id: Iceef860c979f74c2b3ee2d9ae774202964262164
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents e0ea63b4 1ac2d51e
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;
    }
}