diff --git a/core/api/current.txt b/core/api/current.txt index 27d5ac3efa425..56e746501e632 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -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"; diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java new file mode 100644 index 0000000000000..e3f8cb7bb23ea --- /dev/null +++ b/core/java/android/service/credentials/CredentialProviderInfo.java @@ -0,0 +1,184 @@ +/* + * 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 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 getCapabilities() { + return Collections.unmodifiableList(mCapabilities); + } + + /** + * Returns the valid credential provider services available for the user with the + * given {@code userId}. + */ + public static List getAvailableServices(@NonNull Context context, + @UserIdInt int userId) { + final List services = new ArrayList<>(); + + final List 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 getAvailableServicesForCapability( + Context context, @UserIdInt int userId, String credentialType) { + List servicesForCapability = new ArrayList<>(); + final List services = getAvailableServices(context, userId); + + for (CredentialProviderInfo service : services) { + if (service.hasCapability(credentialType)) { + servicesForCapability.add(service); + } + } + return servicesForCapability; + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f0b1b2aa7e3df..7df9864a478f7 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4248,6 +4248,13 @@ + + +