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

Commit be65ffae authored by Reema Bajwa's avatar Reema Bajwa
Browse files

Add CredentialProviderInfo class to get information about credential

providers on the device
Test: manual - built & deployed locally
Bug: 247545196

Change-Id: I86c448fc0adb63f4541ea2f4b46d0b1a5da82453
parent 12639c96
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ package android {
    field public static final String BIND_COMPANION_DEVICE_SERVICE = "android.permission.BIND_COMPANION_DEVICE_SERVICE";
    field public static final String BIND_CONDITION_PROVIDER_SERVICE = "android.permission.BIND_CONDITION_PROVIDER_SERVICE";
    field public static final String BIND_CONTROLS = "android.permission.BIND_CONTROLS";
    field public static final String BIND_CREDENTIAL_PROVIDER_SERVICE = "android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE";
    field public static final String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
    field public static final String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
    field public static final String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE";
+184 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.service.credentials;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * {@link ServiceInfo} and meta-data about a credential provider.
 *
 * @hide
 */
public final class CredentialProviderInfo {
    private static final String TAG = "CredentialProviderInfo";

    @NonNull
    private final ServiceInfo mServiceInfo;
    @NonNull
    private final List<String> mCapabilities;

    // TODO: Move the two strings below to CredentialProviderService when ready.
    private static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
    private static final String SERVICE_INTERFACE =
            "android.service.credentials.CredentialProviderService";


    /**
     * Constructs an information instance of the credential provider.
     *
     * @param context The context object
     * @param serviceComponent The serviceComponent of the provider service
     * @param userId The android userId for which the current process is running
     * @throws PackageManager.NameNotFoundException If provider service is not found
     */
    public CredentialProviderInfo(@NonNull Context context,
            @NonNull ComponentName serviceComponent, int userId)
            throws PackageManager.NameNotFoundException {
        this(context, getServiceInfoOrThrow(serviceComponent, userId));
    }

    private CredentialProviderInfo(@NonNull Context context, @NonNull ServiceInfo serviceInfo) {
        if (!Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE.equals(serviceInfo.permission)) {
            Log.i(TAG, "Credential Provider Service from : " + serviceInfo.packageName
                    + "does not require permission"
                    + Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE);
            throw new SecurityException("Service does not require the expected permission : "
                    + Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE);
        }
        mServiceInfo = serviceInfo;
        mCapabilities = new ArrayList<>();
        populateProviderCapabilities(context);
    }

    private void populateProviderCapabilities(@NonNull Context context) {
        if (mServiceInfo.applicationInfo.metaData == null) {
            return;
        }
        try {
            final int resourceId = mServiceInfo.applicationInfo.metaData.getInt(
                    CAPABILITY_META_DATA_KEY);
            String[] capabilities = context.getResources().getStringArray(resourceId);
            if (capabilities == null) {
                Log.w(TAG, "No capabilities found for provider: " + mServiceInfo.packageName);
                return;
            }
            for (String capability : capabilities) {
                if (capability.isEmpty()) {
                    Log.w(TAG, "Skipping empty capability");
                    continue;
                }
                mCapabilities.add(capability);
            }
        } catch (Resources.NotFoundException e) {
            Log.w(TAG, "Exception while populating provider capabilities: " + e.getMessage());
        }
    }

    private static ServiceInfo getServiceInfoOrThrow(@NonNull ComponentName serviceComponent,
            int userId) throws PackageManager.NameNotFoundException {
        try {
            ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(
                    serviceComponent,
                    PackageManager.GET_META_DATA,
                    userId);
            if (si != null) {
                return si;
            }
        } catch (RemoteException e) {
            Slog.v(TAG, e.getMessage());
        }
        throw new PackageManager.NameNotFoundException(serviceComponent.toString());
    }

    /**
     * Returns true if the service supports the given {@code credentialType}, false otherwise.
     */
    @NonNull
    public boolean hasCapability(@NonNull String credentialType) {
        return mCapabilities.contains(credentialType);
    }

    /** Returns the service info. */
    @NonNull
    public ServiceInfo getServiceInfo() {
        return mServiceInfo;
    }

    /** Returns an immutable list of capabilities this provider service can support. */
    @NonNull
    public List<String> getCapabilities() {
        return Collections.unmodifiableList(mCapabilities);
    }

    /**
     * Returns the valid credential provider services available for the user with the
     * given {@code userId}.
     */
    public static List<CredentialProviderInfo> getAvailableServices(@NonNull Context context,
            @UserIdInt int userId) {
        final List<CredentialProviderInfo> services = new ArrayList<>();

        final List<ResolveInfo> resolveInfos =
                context.getPackageManager().queryIntentServicesAsUser(
                        new Intent(SERVICE_INTERFACE),
                        PackageManager.GET_META_DATA,
                        userId);
        for (ResolveInfo resolveInfo : resolveInfos) {
            final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
            try {
                services.add(new CredentialProviderInfo(context, serviceInfo));
            } catch (SecurityException e) {
                Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e);
            }
        }
        return services;
    }

    /**
     * Returns the valid credential provider services available for the user, that can
     * support the given {@code credentialType}.
     */
    public static List<CredentialProviderInfo> getAvailableServicesForCapability(
            Context context, @UserIdInt int userId, String credentialType) {
        List<CredentialProviderInfo> servicesForCapability = new ArrayList<>();
        final List<CredentialProviderInfo> services = getAvailableServices(context, userId);

        for (CredentialProviderInfo service : services) {
            if (service.hasCapability(credentialType)) {
                servicesForCapability.add(service);
            }
        }
        return servicesForCapability;
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -4248,6 +4248,13 @@
    <permission android:name="android.permission.BIND_AUTOFILL_SERVICE"
        android:protectionLevel="signature" />

    <!-- Must be required by a CredentialProviderService to ensure that only the
         system can bind to it.
         <p>Protection level: signature
    -->
    <permission android:name="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
                android:protectionLevel="signature" />

   <!-- Alternative version of android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE.
        This permission was renamed during the O previews but it was supported on the final O
        release, so we need to carry it over.
+20 −1
Original line number Diff line number Diff line
@@ -17,6 +17,11 @@
package com.android.server.credentials;

import android.annotation.NonNull;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.RemoteException;
import android.util.Log;

import com.android.server.infra.AbstractPerUserSystemService;
@@ -24,7 +29,7 @@ import com.android.server.infra.AbstractPerUserSystemService;
/**
 * Per-user implementation of {@link CredentialManagerService}
 */
public class CredentialManagerServiceImpl extends
public final class CredentialManagerServiceImpl extends
        AbstractPerUserSystemService<CredentialManagerServiceImpl, CredentialManagerService> {
    private static final String TAG = "CredManSysServiceImpl";

@@ -34,6 +39,20 @@ public class CredentialManagerServiceImpl extends
        super(master, lock, userId);
    }

    @Override // from PerUserSystemService
    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
            throws PackageManager.NameNotFoundException {
        ServiceInfo si;
        try {
            si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
                    PackageManager.GET_META_DATA, mUserId);
        } catch (RemoteException e) {
            throw new PackageManager.NameNotFoundException(
                    "Could not get service for " + serviceComponent);
        }
        return si;
    }

    /**
     * Unimplemented getCredentials
     */