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

Commit 4b371e3d authored by Becca Hughes's avatar Becca Hughes
Browse files

Add subtitle to settings (framework)

Allows a credential provider to show a
subtitle/summary underneath the title in
the list of providers.

Test: ondevice & atest & cts
Bug: 253157366
Change-Id: Iae541c050d04412018a4f37f60140454723f7aa4
parent 3f4bdc7c
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1089,6 +1089,7 @@ package android.credentials {
    method @Nullable public CharSequence getLabel(@NonNull android.content.Context);
    method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context);
    method @NonNull public android.content.pm.ServiceInfo getServiceInfo();
    method @Nullable public CharSequence getSettingsSubtitle();
    method @NonNull public boolean hasCapability(@NonNull String);
    method public boolean isEnabled();
    method public boolean isSystemProvider();
@@ -1101,6 +1102,7 @@ package android.credentials {
    method @NonNull public android.credentials.CredentialProviderInfo.Builder addCapabilities(@NonNull java.util.List<java.lang.String>);
    method @NonNull public android.credentials.CredentialProviderInfo build();
    method @NonNull public android.credentials.CredentialProviderInfo.Builder setEnabled(boolean);
    method @NonNull public android.credentials.CredentialProviderInfo.Builder setSettingsSubtitle(@Nullable CharSequence);
    method @NonNull public android.credentials.CredentialProviderInfo.Builder setSystemProvider(boolean);
  }

+20 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ public final class CredentialProviderInfo implements Parcelable {
    @NonNull private final ServiceInfo mServiceInfo;
    @NonNull private final List<String> mCapabilities = new ArrayList<>();
    @Nullable private final CharSequence mOverrideLabel;
    @Nullable private CharSequence mSettingsSubtitle = null;
    private final boolean mIsSystemProvider;
    private final boolean mIsEnabled;

@@ -53,6 +54,7 @@ public final class CredentialProviderInfo implements Parcelable {
        mServiceInfo = builder.mServiceInfo;
        mCapabilities.addAll(builder.mCapabilities);
        mIsSystemProvider = builder.mIsSystemProvider;
        mSettingsSubtitle = builder.mSettingsSubtitle;
        mIsEnabled = builder.mIsEnabled;
        mOverrideLabel = builder.mOverrideLabel;
    }
@@ -100,6 +102,12 @@ public final class CredentialProviderInfo implements Parcelable {
        return mIsEnabled;
    }

    /** Returns the settings subtitle. */
    @Nullable
    public CharSequence getSettingsSubtitle() {
        return mSettingsSubtitle;
    }

    /** Returns the component name for the service. */
    @NonNull
    public ComponentName getComponentName() {
@@ -113,6 +121,7 @@ public final class CredentialProviderInfo implements Parcelable {
        dest.writeStringList(mCapabilities);
        dest.writeBoolean(mIsEnabled);
        TextUtils.writeToParcel(mOverrideLabel, dest, flags);
        TextUtils.writeToParcel(mSettingsSubtitle, dest, flags);
    }

    @Override
@@ -135,6 +144,9 @@ public final class CredentialProviderInfo implements Parcelable {
                + "overrideLabel="
                + mOverrideLabel
                + ", "
                + "settingsSubtitle="
                + mSettingsSubtitle
                + ", "
                + "capabilities="
                + String.join(",", mCapabilities)
                + "}";
@@ -146,6 +158,7 @@ public final class CredentialProviderInfo implements Parcelable {
        in.readStringList(mCapabilities);
        mIsEnabled = in.readBoolean();
        mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
        mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    }

    public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR =
@@ -167,6 +180,7 @@ public final class CredentialProviderInfo implements Parcelable {
        @NonNull private ServiceInfo mServiceInfo;
        @NonNull private List<String> mCapabilities = new ArrayList<>();
        private boolean mIsSystemProvider = false;
        @Nullable private CharSequence mSettingsSubtitle = null;
        private boolean mIsEnabled = false;
        @Nullable private CharSequence mOverrideLabel = null;

@@ -195,6 +209,12 @@ public final class CredentialProviderInfo implements Parcelable {
            return this;
        }

        /** Sets the settings subtitle. */
        public @NonNull Builder setSettingsSubtitle(@Nullable CharSequence settingsSubtitle) {
            mSettingsSubtitle = settingsSubtitle;
            return this;
        }

        /** Sets a list of capabilities this provider service can support. */
        public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) {
            mCapabilities.addAll(capabilities);
+158 −22
Original line number Diff line number Diff line
@@ -33,15 +33,27 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
import android.util.Xml;

import com.android.internal.R;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -56,6 +68,11 @@ import java.util.Set;
public final class CredentialProviderInfoFactory {
    private static final String TAG = "CredentialProviderInfoFactory";

    private static final String TAG_CREDENTIAL_PROVIDER = "credential-provider";
    private static final String TAG_CAPABILITIES = "capabilities";
    private static final String TAG_CAPABILITY = "capability";
    private static final String ATTR_NAME = "name";

    /**
     * Constructs an information instance of the credential provider.
     *
@@ -172,10 +189,8 @@ public final class CredentialProviderInfoFactory {
    private static CredentialProviderInfo.Builder populateMetadata(
            @NonNull Context context, ServiceInfo serviceInfo) {
        requireNonNull(context, "context must not be null");

        final CredentialProviderInfo.Builder builder =
                new CredentialProviderInfo.Builder(serviceInfo);
        final PackageManager pm = context.getPackageManager();
        CredentialProviderInfo.Builder builder = new CredentialProviderInfo.Builder(serviceInfo);

        // 1. Get the metadata for the service.
        final Bundle metadata = serviceInfo.metaData;
@@ -184,46 +199,166 @@ public final class CredentialProviderInfoFactory {
            return builder;
        }

        // 2. Extract the capabilities from the bundle.
        // 2. Get the resources for the application.
        Resources resources = null;
        try {
            Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
            resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "Failed to get app resources", e);
        }

        // 3. Stop if we are missing data.
        if (metadata == null || resources == null) {
            Log.i(TAG, "populateMetadata - resources is null");
            return builder;
        }

            builder.addCapabilities(populateProviderCapabilities(resources, metadata, serviceInfo));
        } catch (PackageManager.NameNotFoundException e) {
            Slog.e(TAG, e.getMessage());
        // 4. Extract the XML metadata.
        try {
            builder = extractXmlMetadata(context, serviceInfo, pm, resources);
        } catch (Exception e) {
            Log.e(TAG, "Failed to get XML metadata", e);
        }

        // 5. Extract the legacy metadata.
        try {
            builder.addCapabilities(
                    populateLegacyProviderCapabilities(resources, metadata, serviceInfo));
        } catch (Exception e) {
            Log.e(TAG, "Failed to get legacy metadata ", e);
        }

        return builder;
    }

    private static CredentialProviderInfo.Builder extractXmlMetadata(
            @NonNull Context context,
            @NonNull ServiceInfo serviceInfo,
            @NonNull PackageManager pm,
            @NonNull Resources resources) {
        final CredentialProviderInfo.Builder builder =
                new CredentialProviderInfo.Builder(serviceInfo);
        final XmlResourceParser parser =
                serviceInfo.loadXmlMetaData(pm, CredentialProviderService.SERVICE_META_DATA);
        if (parser == null) {
            return builder;
        }

        try {
            int type = 0;
            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
                type = parser.next();
            }

            // This is matching a <credential-provider /> tag in the XML.
            if (TAG_CREDENTIAL_PROVIDER.equals(parser.getName())) {
                final AttributeSet allAttributes = Xml.asAttributeSet(parser);
                TypedArray afsAttributes = null;
                try {
                    afsAttributes =
                            resources.obtainAttributes(
                                    allAttributes,
                                    com.android.internal.R.styleable.CredentialProvider);
                    builder.setSettingsSubtitle(
                            afsAttributes.getString(
                                    R.styleable.CredentialProvider_settingsSubtitle));
                } catch (Exception e) {
                    Log.e(TAG, "Failed to get XML attr", e);
                } finally {
                    if (afsAttributes != null) {
                        afsAttributes.recycle();
                    }
                }
                builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources));
            } else {
                Log.e(TAG, "Meta-data does not start with credential-provider-service tag");
            }
        } catch (IOException | XmlPullParserException e) {
            Log.e(TAG, "Error parsing credential provider service meta-data", e);
        }

        return builder;
    }

    private static List<String> populateProviderCapabilities(
    private static List<String> parseXmlProviderOuterCapabilities(
            XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException {
        final List<String> capabilities = new ArrayList<>();
        final int outerDepth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            if (TAG_CAPABILITIES.equals(parser.getName())) {
                capabilities.addAll(parseXmlProviderInnerCapabilities(parser, resources));
            }
        }

        return capabilities;
    }

    private static List<String> parseXmlProviderInnerCapabilities(
            XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException {
        List<String> capabilities = new ArrayList<>();

        final int outerDepth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            if (TAG_CAPABILITY.equals(parser.getName())) {
                String name = parser.getAttributeValue(null, ATTR_NAME);
                if (name != null && !TextUtils.isEmpty(name)) {
                    capabilities.add(name);
                }
            }
        }

        return capabilities;
    }

    private static List<String> populateLegacyProviderCapabilities(
            Resources resources, Bundle metadata, ServiceInfo serviceInfo) {
        List<String> output = new ArrayList<>();
        String[] capabilities = new String[0];
        List<String> capabilities = new ArrayList<>();

        try {
            capabilities =
            String[] discovered =
                    resources.getStringArray(
                            metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY));
        } catch (Resources.NotFoundException e) {
            Slog.e(TAG, "Failed to get capabilities: " + e.getMessage());
            if (discovered != null) {
                capabilities.addAll(Arrays.asList(discovered));
            }
        } catch (Resources.NotFoundException | NullPointerException e) {
            Log.e(TAG, "Failed to get capabilities: ", e);
        }

        try {
            String[] discovered =
                    metadata.getStringArray(CredentialProviderService.CAPABILITY_META_DATA_KEY);
            if (discovered != null) {
                capabilities.addAll(Arrays.asList(discovered));
            }
        } catch (Resources.NotFoundException | NullPointerException e) {
            Log.e(TAG, "Failed to get capabilities: ", e);
        }

        if (capabilities == null || capabilities.length == 0) {
            Slog.e(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
        if (capabilities.size() == 0) {
            Log.e(TAG, "No capabilities found for provider:" + serviceInfo);
            return output;
        }

        for (String capability : capabilities) {
            if (capability.isEmpty()) {
                Slog.e(TAG, "Skipping empty capability");
            if (capability == null || capability.isEmpty()) {
                Log.w(TAG, "Skipping empty/null capability");
                continue;
            }
            Slog.e(TAG, "Capabilities found for provider: " + capability);
            Log.i(TAG, "Capabilities found for provider: " + capability);
            output.add(capability);
        }
        return output;
@@ -339,7 +474,8 @@ public final class CredentialProviderInfoFactory {

        try {
            DevicePolicyManager dpm = newContext.getSystemService(DevicePolicyManager.class);
            return dpm.getCredentialManagerPolicy();
            PackagePolicy pp = dpm.getCredentialManagerPolicy();
            return pp;
        } catch (SecurityException e) {
            // If the current user is not enrolled in DPM then this can throw a security error.
            Log.e(TAG, "Failed to get device policy: " + e);
+0 −1
Original line number Diff line number Diff line
@@ -192,7 +192,6 @@ public abstract class CredentialProviderService extends Service {
      */
    public static final String SERVICE_META_DATA = "android.credentials.provider";


    /** @hide */
    public static final String TEST_SYSTEM_PROVIDER_META_DATA_KEY =
            "android.credentials.testsystemprovider";