diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1086577b0a391..23897bba4c1f2 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1585,6 +1585,11 @@ package android.provider {
package android.security {
+ public final class KeyChain {
+ method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") public static boolean removeCredentialManagementApp(@NonNull android.content.Context);
+ method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") public static boolean setCredentialManagementApp(@NonNull android.content.Context, @NonNull String, @NonNull android.security.AppUriAuthenticationPolicy);
+ }
+
public class KeyStoreException extends java.lang.Exception {
ctor public KeyStoreException(int, String);
method public int getErrorCode();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8682fea1f8dc6..c1e93e7b9f3cc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3147,6 +3147,11 @@
There can only be one credential management on the device. If another app requests to
+ * become the credential management app, then the existing credential management app will
+ * no longer be able to manage credentials.
+ *
+ * @param packageName The package name of the credential management app
+ * @param authenticationPolicy The authentication policy of the credential management app. This
+ * policy determines which alias for a private key and certificate
+ * pair should be used for authentication.
+ * @return {@code true} if the credential management app was successfully added.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP)
+ public static boolean setCredentialManagementApp(@NonNull Context context,
+ @NonNull String packageName, @NonNull AppUriAuthenticationPolicy authenticationPolicy) {
+ try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
+ keyChainConnection.getService()
+ .setCredentialManagementApp(packageName, authenticationPolicy);
+ return true;
+ } catch (RemoteException | InterruptedException e) {
+ Log.w(LOG, "Set credential management app failed", e);
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
+ /**
+ * Remove the user's KeyChain credentials on unmanaged devices.
+ *
+ * @return {@code true} if the credential management app was successfully removed.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP)
+ public static boolean removeCredentialManagementApp(@NonNull Context context) {
+ try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
+ keyChainConnection.getService().removeCredentialManagementApp();
+ return true;
+ } catch (RemoteException | InterruptedException e) {
+ Log.w(LOG, "Remove credential management app failed", e);
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
private static class AliasResponse extends IKeyChainAliasCallback.Stub {
private final KeyChainAliasCallback keyChainAliasResponse;
private AliasResponse(KeyChainAliasCallback keyChainAliasResponse) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index f83f6706d55b9..d221819963993 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -119,6 +119,7 @@