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

Commit 1f33ec6d authored by Becca Hughes's avatar Becca Hughes
Browse files

Add API to check if credman is enabled

Adds an API that can be called by provider apps
to see if they are enabled.

Test: Make
Bug: 253157366
API-Coverage-Bug: 247549381
Change-Id: I61e64b21437165303ca16656b86c8814b27e26b7
parent 27bd04e2
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -13507,6 +13507,7 @@ package android.credentials {
    method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
    method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
    method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
    method public boolean isEnabledCredentialProviderService(@NonNull android.content.ComponentName);
    method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest);
    method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest);
  }
+35 −24
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
import android.os.CancellationSignal;
@@ -279,6 +280,25 @@ public final class CredentialManager {
        }
    }

    /**
     * Returns {@code true} if the calling application provides a CredentialProviderService that is
     * enabled for the current user, or {@code false} otherwise. CredentialProviderServices are
     * enabled on a per-service basis so the individual component name of the service should be
     * passed in here.
     *
     * @param componentName the component name to check is enabled
     */
    public boolean isEnabledCredentialProviderService(@NonNull ComponentName componentName) {
        requireNonNull(componentName, "componentName must not be null");

        try {
            return mService.isEnabledCredentialProviderService(
                    componentName, mContext.getOpPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns whether the service is enabled.
     *
@@ -298,8 +318,7 @@ public final class CredentialManager {

    private boolean isServiceEnabled() {
        return DeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER,
                true);
                DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
    }

    /**
@@ -313,22 +332,19 @@ public final class CredentialManager {
    }

    /**
     * Registers a {@link CredentialDescription} for an actively provisioned {@link Credential}
     * a CredentialProvider has. This registry will then be used to determine where to
     * fetch the requested {@link Credential} from. Not all credential types will be supported.
     * The distinction will be made by the JetPack layer. For the types that are supported,
     * JetPack will add a new key-value pair into {@link GetCredentialRequest}. These will not
     * be persistent on the device. The Credential Providers will need to call this API again
     * upon device reboot.
     * Registers a {@link CredentialDescription} for an actively provisioned {@link Credential} a
     * CredentialProvider has. This registry will then be used to determine where to fetch the
     * requested {@link Credential} from. Not all credential types will be supported. The
     * distinction will be made by the JetPack layer. For the types that are supported, JetPack will
     * add a new key-value pair into {@link GetCredentialRequest}. These will not be persistent on
     * the device. The Credential Providers will need to call this API again upon device reboot.
     *
     * @param request the request data
     *
     * @throws {@link UnsupportedOperationException} if the feature has not been enabled.
     * @throws {@link  com.android.server.credentials.NonCredentialProviderCallerException}
     * if the calling package name is not also listed as a Credential Provider.
     * @throws {@link com.android.server.credentials.NonCredentialProviderCallerException} if the
     *     calling package name is not also listed as a Credential Provider.
     * @throws {@link IllegalArgumentException} if the calling Credential Provider can not handle
     *     one or more of the Credential Types that are sent for registration.
     *
     */
    public void registerCredentialDescription(
            @NonNull RegisterCredentialDescriptionRequest request) {
@@ -346,16 +362,12 @@ public final class CredentialManager {
        }
    }


    /**
     * Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential}
     * that has been registered previously.
     *
     *
     * @param request the request data
     *
     * @throws {@link UnsupportedOperationException} if the feature has not been enabled.
     *
     */
    public void unregisterCredentialDescription(
            @NonNull UnregisterCredentialDescriptionRequest request) {
@@ -371,7 +383,6 @@ public final class CredentialManager {
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }

    }

    private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
+3 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
import android.credentials.IListEnabledProvidersCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.content.ComponentName;
import android.os.ICancellationSignal;

/**
@@ -50,5 +51,7 @@ interface ICredentialManager {
    void registerCredentialDescription(in RegisterCredentialDescriptionRequest request, String callingPackage);

    void unregisterCredentialDescription(in UnregisterCredentialDescriptionRequest request, String callingPackage);

    boolean isEnabledCredentialProviderService(in ComponentName componentName, String callingPackage);
}
+144 −82
Original line number Diff line number Diff line
@@ -84,14 +84,11 @@ public final class CredentialManagerService

    private final Context mContext;

    /**
     * Cache of system service list per user id.
     */
    /** Cache of system service list per user id. */
    @GuardedBy("mLock")
    private final SparseArray<List<CredentialManagerServiceImpl>> mSystemServicesCacheList =
            new SparseArray<>();


    public CredentialManagerService(@NonNull Context context) {
        super(
                context,
@@ -109,8 +106,10 @@ public final class CredentialManagerService
        List<CredentialManagerServiceImpl> services = new ArrayList<>();
        List<CredentialProviderInfo> credentialProviderInfos =
                CredentialProviderInfo.getAvailableSystemServices(mContext, resolvedUserId);
        credentialProviderInfos.forEach(info -> {
            services.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
        credentialProviderInfos.forEach(
                info -> {
                    services.add(
                            new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
                });
        return services;
    }
@@ -174,10 +173,9 @@ public final class CredentialManagerService
        CredentialManagerServiceImpl serviceToBeRemoved = null;
        for (CredentialManagerServiceImpl service : services) {
            if (service != null) {
                CredentialProviderInfo credentialProviderInfo =
                        service.getCredentialProviderInfo();
                ComponentName componentName = credentialProviderInfo.getServiceInfo()
                        .getComponentName();
                CredentialProviderInfo credentialProviderInfo = service.getCredentialProviderInfo();
                ComponentName componentName =
                        credentialProviderInfo.getServiceInfo().getComponentName();
                if (packageName.equals(componentName.getPackageName())) {
                    serviceToBeRemoved = service;
                    removeServiceFromMultiModeSettings(componentName.flattenToString(), userId);
@@ -193,7 +191,6 @@ public final class CredentialManagerService
        // TODO("Iterate over system services and remove if needed")
    }


    @GuardedBy("mLock")
    private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
            int resolvedUserId) {
@@ -222,8 +219,7 @@ public final class CredentialManagerService
    }

    @GuardedBy("mLock")
    private List<CredentialManagerServiceImpl> getAllCredentialProviderServicesLocked(
            int userId) {
    private List<CredentialManagerServiceImpl> getAllCredentialProviderServicesLocked(int userId) {
        List<CredentialManagerServiceImpl> concatenatedServices = new ArrayList<>();
        List<CredentialManagerServiceImpl> userConfigurableServices =
                getServiceListForUserLocked(userId);
@@ -243,11 +239,13 @@ public final class CredentialManagerService
    // to be guarded by 'service.mLock', which is the same as mLock.
    private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
            GetRequestSession session,
            List<String> requestOptions, Set<String> activeCredentialContainers) {
            List<String> requestOptions,
            Set<String> activeCredentialContainers) {
        List<ProviderSession> providerSessions = new ArrayList<>();
        // Invoke all services of a user to initiate a provider session
        for (String packageName : activeCredentialContainers) {
            providerSessions.add(ProviderRegistryGetSession.createNewSession(
            providerSessions.add(
                    ProviderRegistryGetSession.createNewSession(
                            mContext,
                            UserHandle.getCallingUserId(),
                            session,
@@ -260,16 +258,17 @@ public final class CredentialManagerService
    @NonNull
    private Set<String> getFilteredResultFromRegistry(List<CredentialOption> options) {
        // Session for active/provisioned credential descriptions;
        CredentialDescriptionRegistry registry = CredentialDescriptionRegistry
                .forUser(UserHandle.getCallingUserId());
        CredentialDescriptionRegistry registry =
                CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());

        // All requested credential descriptions based on the given request.
        Set<String> requestedCredentialDescriptions =
                options.stream().map(
                        getCredentialOption -> getCredentialOption
                options.stream()
                        .map(
                                getCredentialOption ->
                                        getCredentialOption
                                                .getCredentialRetrievalData()
                                        .getString(CredentialOption
                                                .FLATTENED_REQUEST))
                                                .getString(CredentialOption.FLATTENED_REQUEST))
                        .collect(Collectors.toSet());

        // All requested credential descriptions based on the given request.
@@ -306,9 +305,13 @@ public final class CredentialManagerService
    private CallingAppInfo constructCallingAppInfo(String packageName, int userId) {
        final PackageInfo packageInfo;
        try {
            packageInfo = getContext().getPackageManager().getPackageInfoAsUser(
            packageInfo =
                    getContext()
                            .getPackageManager()
                            .getPackageInfoAsUser(
                                    packageName,
                    PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES),
                                    PackageManager.PackageInfoFlags.of(
                                            PackageManager.GET_SIGNING_CERTIFICATES),
                                    userId);
        } catch (PackageManager.NameNotFoundException e) {
            Log.i(TAG, "Issue while retrieving signatureInfo : " + e.getMessage());
@@ -345,33 +348,46 @@ public final class CredentialManagerService
            if (isCredentialDescriptionApiEnabled()) {
                List<CredentialOption> optionsThatRequireActiveCredentials =
                        request.getCredentialOptions().stream()
                                .filter(getCredentialOption ->
                                        !TextUtils.isEmpty(getCredentialOption
                                                .getCredentialRetrievalData().getString(
                                .filter(
                                        getCredentialOption ->
                                                !TextUtils.isEmpty(
                                                        getCredentialOption
                                                                .getCredentialRetrievalData()
                                                                .getString(
                                                                        CredentialOption
                                                        .FLATTENED_REQUEST, null)))
                                                                                .FLATTENED_REQUEST,
                                                                        null)))
                                .toList();

                List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
                        request.getCredentialOptions().stream()
                                .filter(getCredentialOption ->
                                        TextUtils.isEmpty(getCredentialOption
                                                .getCredentialRetrievalData().getString(
                                .filter(
                                        getCredentialOption ->
                                                TextUtils.isEmpty(
                                                        getCredentialOption
                                                                .getCredentialRetrievalData()
                                                                .getString(
                                                                        CredentialOption
                                                                .FLATTENED_REQUEST, null)))
                                                                                .FLATTENED_REQUEST,
                                                                        null)))
                                .toList();

                List<ProviderSession> sessionsWithoutRemoteService =
                        initiateProviderSessionsWithActiveContainers(session,
                                optionsThatRequireActiveCredentials
                                        .stream().map(getCredentialOption ->
                                                getCredentialOption.getCredentialRetrievalData()
                                                        .getString(CredentialOption
                        initiateProviderSessionsWithActiveContainers(
                                session,
                                optionsThatRequireActiveCredentials.stream()
                                        .map(
                                                getCredentialOption ->
                                                        getCredentialOption
                                                                .getCredentialRetrievalData()
                                                                .getString(
                                                                        CredentialOption
                                                                                .FLATTENED_REQUEST))
                                        .collect(Collectors.toList()),
                                getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));

                List<ProviderSession> sessionsWithRemoteService = initiateProviderSessions(
                List<ProviderSession> sessionsWithRemoteService =
                        initiateProviderSessions(
                                session,
                                optionsThatDoNotRequireActiveCredentials.stream()
                                        .map(CredentialOption::getType)
@@ -394,7 +410,8 @@ public final class CredentialManagerService

            if (providerSessions.isEmpty()) {
                try {
                    callback.onError(GetCredentialException.TYPE_NO_CREDENTIAL,
                    callback.onError(
                            GetCredentialException.TYPE_NO_CREDENTIAL,
                            "No credentials available on this device.");
                } catch (RemoteException e) {
                    Log.i(
@@ -437,7 +454,8 @@ public final class CredentialManagerService

            if (providerSessions.isEmpty()) {
                try {
                    callback.onError(CreateCredentialException.TYPE_NO_CREDENTIAL,
                    callback.onError(
                            CreateCredentialException.TYPE_NO_CREDENTIAL,
                            "No credentials available on this device.");
                } catch (RemoteException e) {
                    Log.i(
@@ -521,6 +539,37 @@ public final class CredentialManagerService
            getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent());
        }

        @Override
        public boolean isEnabledCredentialProviderService(
                ComponentName componentName, String callingPackage) {
            Log.i(TAG, "isEnabledCredentialProviderService");

            // TODO(253157366): Check additional set of services.
            final int userId = UserHandle.getCallingUserId();
            synchronized (mLock) {
                final List<CredentialManagerServiceImpl> services =
                        getServiceListForUserLocked(userId);
                for (CredentialManagerServiceImpl s : services) {
                    final ComponentName serviceComponentName = s.getServiceComponentName();

                    if (serviceComponentName.equals(componentName)) {
                        if (!s.getServicePackageName().equals(callingPackage)) {
                            // The component name and the package name do not match.
                            Log.w(
                                    TAG,
                                    "isEnabledCredentialProviderService: Component name does not"
                                            + " match package name.");
                            return false;
                        }

                        return true;
                    }
                }
            }

            return false;
        }

        @Override
        public ICancellationSignal clearCredentialState(
                ClearCredentialStateRequest request,
@@ -550,8 +599,7 @@ public final class CredentialManagerService
            if (providerSessions.isEmpty()) {
                try {
                    // TODO("Replace with properly defined error type")
                    callback.onError("UNKNOWN", "No crdentials available on this "
                            + "device");
                    callback.onError("UNKNOWN", "No crdentials available on this " + "device");
                } catch (RemoteException e) {
                    Log.i(
                            TAG,
@@ -573,35 +621,46 @@ public final class CredentialManagerService
            Log.i(TAG, "registerCredentialDescription");

            List<CredentialProviderInfo> services =
                    CredentialProviderInfo.getAvailableServices(mContext,
                            UserHandle.getCallingUserId());
                    CredentialProviderInfo.getAvailableServices(
                            mContext, UserHandle.getCallingUserId());

            List<String> providers = services.stream()
                    .map(credentialProviderInfo
                            -> credentialProviderInfo.getServiceInfo().packageName).toList();
            List<String> providers =
                    services.stream()
                            .map(
                                    credentialProviderInfo ->
                                            credentialProviderInfo.getServiceInfo().packageName)
                            .toList();

            if (!providers.contains(callingPackage)) {
                throw new NonCredentialProviderCallerException(callingPackage);
            }

            List<CredentialProviderInfo> matchingService = services.stream().filter(
            List<CredentialProviderInfo> matchingService =
                    services.stream()
                            .filter(
                                    credentialProviderInfo ->
                            credentialProviderInfo.getServiceInfo()
                                    .packageName.equals(callingPackage)).toList();
                                            credentialProviderInfo
                                                    .getServiceInfo()
                                                    .packageName
                                                    .equals(callingPackage))
                            .toList();

            CredentialProviderInfo credentialProviderInfo = matchingService.get(0);

            Set<String> supportedTypes = request.getCredentialDescriptions()
                    .stream().map(CredentialDescription::getType).filter(
                            credentialProviderInfo::hasCapability).collect(Collectors.toSet());
            Set<String> supportedTypes =
                    request.getCredentialDescriptions().stream()
                            .map(CredentialDescription::getType)
                            .filter(credentialProviderInfo::hasCapability)
                            .collect(Collectors.toSet());

            if (supportedTypes.size() != request.getCredentialDescriptions().size()) {
                throw new IllegalArgumentException("CredentialProvider does not support one or more"
                throw new IllegalArgumentException(
                        "CredentialProvider does not support one or more"
                                + "of the registered types. Check your XML entry.");
            }

            CredentialDescriptionRegistry session = CredentialDescriptionRegistry
                    .forUser(UserHandle.getCallingUserId());
            CredentialDescriptionRegistry session =
                    CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());

            session.executeRegisterRequest(request, callingPackage);
        }
@@ -614,19 +673,22 @@ public final class CredentialManagerService
            ICancellationSignal cancelTransport = CancellationSignal.createTransport();

            List<CredentialProviderInfo> services =
                    CredentialProviderInfo.getAvailableServices(mContext,
                            UserHandle.getCallingUserId());
                    CredentialProviderInfo.getAvailableServices(
                            mContext, UserHandle.getCallingUserId());

            List<String> providers = services.stream()
                    .map(credentialProviderInfo
                            -> credentialProviderInfo.getServiceInfo().packageName).toList();
            List<String> providers =
                    services.stream()
                            .map(
                                    credentialProviderInfo ->
                                            credentialProviderInfo.getServiceInfo().packageName)
                            .toList();

            if (!providers.contains(callingPackage)) {
                throw new NonCredentialProviderCallerException(callingPackage);
            }

            CredentialDescriptionRegistry session = CredentialDescriptionRegistry
                    .forUser(UserHandle.getCallingUserId());
            CredentialDescriptionRegistry session =
                    CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());

            session.executeUnregisterRequest(request, callingPackage);
        }