Chapter 41: Credential Manager and Passkeys¶
The Credential Manager framework, introduced in Android 14, provides a unified API for managing user credentials -- passwords, passkeys (FIDO2/WebAuthn), federated sign-in tokens, and digital identity documents. It replaces the fragmented landscape of individual autofill services and proprietary sign-in SDKs with a single, pluggable system service that mediates between requesting apps and credential provider apps.
This chapter traces the complete architecture from the client-facing
CredentialManager API through the system service, provider sessions, the selection
UI, and the provider-side CredentialProviderService. We ground every description in
the real AOSP source under frameworks/base/services/credentials/ and
frameworks/base/core/java/android/credentials/.
41.1 Credential Manager Architecture¶
41.1.1 Problem Statement¶
Before Credential Manager, credential retrieval involved multiple disconnected mechanisms:
| Mechanism | Limitation |
|---|---|
AccountManager |
Only managed account tokens; no standardized passkey support |
Autofill Framework (AutofillService) |
Designed for filling views, not modern credential types |
| FIDO2 libraries (Play Services) | Proprietary; not available on AOSP builds |
| Third-party password managers | Each required its own integration path |
Apps needed to call different APIs for passwords versus passkeys versus federated credentials. Users had to configure each mechanism separately.
41.1.2 Design Goals¶
The Credential Manager was designed around these principles:
- Single API surface -- One call to retrieve any credential type
- Pluggable providers -- Any app can register as a credential provider
- System-mediated selection -- The system controls the credential picker UI
- Two-phase protocol -- An initial "begin" query followed by user-selected finalization
- Per-user isolation -- Each Android user has independent provider configurations
41.1.3 High-Level Architecture¶
graph TB
subgraph "Client App Process"
CA[Client App]
CM[CredentialManager API]
end
subgraph "system_server"
CMS[CredentialManagerService]
CMSI["CredentialManagerServiceImpl<br/>per-user, per-provider"]
RS["RequestSession<br/>GetRequestSession / CreateRequestSession"]
PS["ProviderSession<br/>ProviderGetSession / ProviderCreateSession"]
RCS["RemoteCredentialService<br/>ServiceConnector"]
CMUI[CredentialManagerUi]
CDR[CredentialDescriptionRegistry]
end
subgraph "Provider App Process"
CPS[CredentialProviderService]
STORE["(Credential Store)"]
end
subgraph "UI Process"
SEL[Credential Selector Activity]
end
CA --> CM
CM -->|Binder IPC| CMS
CMS --> CMSI
CMS --> RS
RS --> PS
PS --> RCS
RCS -->|Bind & Call| CPS
CPS --> STORE
RS --> CMUI
CMUI --> SEL
SEL -->|User Selection| RS
CMS --> CDR
style CMS fill:#e1f5fe
style RS fill:#fff3e0
style PS fill:#fff3e0
style CPS fill:#e8f5e9
Source file locations:
| Component | Path |
|---|---|
CredentialManagerService |
frameworks/base/services/credentials/java/com/android/server/credentials/CredentialManagerService.java |
CredentialManagerServiceImpl |
frameworks/base/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java |
RequestSession |
frameworks/base/services/credentials/java/com/android/server/credentials/RequestSession.java |
ProviderSession |
frameworks/base/services/credentials/java/com/android/server/credentials/ProviderSession.java |
RemoteCredentialService |
frameworks/base/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java |
CredentialManagerUi |
frameworks/base/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java |
CredentialProviderService |
frameworks/base/core/java/android/service/credentials/CredentialProviderService.java |
Credential |
frameworks/base/core/java/android/credentials/Credential.java |
41.1.4 Key Abstractions¶
The framework introduces several layers of abstraction that allow the system to handle diverse credential types through a uniform protocol:
Credential -- A typed container holding credential data. The Credential class
(frameworks/base/core/java/android/credentials/Credential.java) carries a type
string and a Bundle of data:
// From Credential.java
public final class Credential implements Parcelable {
public static final String TYPE_PASSWORD_CREDENTIAL =
"android.credentials.TYPE_PASSWORD_CREDENTIAL";
private final String mType;
private final Bundle mData;
}
Specific credential types are identified by string constants:
| Type Constant | Credential Kind |
|---|---|
TYPE_PASSWORD_CREDENTIAL |
Username/password pair |
"androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" |
Passkey (FIDO2/WebAuthn) |
"com.credman.IdentityCredential" |
Digital identity document |
| Custom type strings | Provider-defined credentials |
CredentialOption -- Specifies what the client app is requesting. Each option
carries a type, retrieval data (a Bundle), and candidate query data.
CredentialProviderInfo -- Metadata about an installed credential provider,
including its ComponentName, capabilities (supported credential types), and whether
it is a system provider.
41.1.5 The Two-Phase Protocol¶
A fundamental design choice is the two-phase communication between system_server and credential providers:
sequenceDiagram
participant App as Client App
participant CMS as CredentialManagerService
participant Prov as CredentialProviderService
participant UI as Selector UI
App->>CMS: getCredential(request)
Note over CMS: Phase 1: Begin (Query)
CMS->>Prov: onBeginGetCredential(beginRequest)
Prov-->>CMS: BeginGetCredentialResponse<br/>(credential entries, auth actions)
CMS->>UI: Show credential selector
UI-->>CMS: User selects an entry
Note over CMS: Phase 2: Finalize
CMS->>Prov: PendingIntent fires provider Activity
Note over Prov: Provider retrieves full credential
Prov-->>CMS: GetCredentialResponse(credential)
CMS-->>App: GetCredentialResponse
Phase 1 (Begin/Query): The system sends a BeginGetCredentialRequest to each
enabled provider. Providers respond with lightweight metadata -- credential entries
describing available credentials, authentication actions if the provider is locked,
and optional remote entries. No actual credential data is exchanged yet.
Phase 2 (Finalize): After the user selects an entry from the system UI, the
system fires the PendingIntent attached to that entry. The provider's Activity
retrieves the full credential (possibly after biometric verification) and returns
it via Activity.setResult().
This two-phase approach has important security properties:
- Credential material is never loaded into memory until the user explicitly selects it
- The system never holds raw credentials; it only brokers metadata
- Providers can require authentication (unlock, biometrics) before revealing data
41.1.6 Service Registration and Discovery¶
Credential Manager is registered as a system service during SystemServer startup:
// From CredentialManagerService.java
@Override // from SystemService
public void onStart() {
publishBinderService(CREDENTIAL_SERVICE, new CredentialManagerServiceStub());
}
The service name is Context.CREDENTIAL_SERVICE, making it accessible via:
41.2 CredentialManagerService¶
41.2.1 Service Hierarchy¶
CredentialManagerService extends AbstractMasterSystemService, a framework
pattern for services that manage per-user child services. The class hierarchy is:
classDiagram
class SystemService {
+onStart()
+onUserStopped()
}
class AbstractMasterSystemService {
#newServiceListLocked()
#getServiceListForUserLocked()
#mLock : Object
}
class CredentialManagerService {
-mSystemServicesCacheList : SparseArray
-mRequestSessions : SparseArray
-mSessionManager : SessionManager
+onStart()
}
class CredentialManagerServiceImpl {
-mInfo : CredentialProviderInfo
+initiateProviderSessionForRequestLocked()
+isServiceCapableLocked()
}
SystemService <|-- AbstractMasterSystemService
AbstractMasterSystemService <|-- CredentialManagerService
CredentialManagerService "1" --> "*" CredentialManagerServiceImpl : manages per-user
Source: frameworks/base/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
41.2.2 Constructor and Settings Resolver¶
The constructor wires up the settings-based provider resolution:
// From CredentialManagerService.java (line ~130)
public CredentialManagerService(@NonNull Context context) {
super(
context,
new SecureSettingsServiceNameResolver(
context, Settings.Secure.CREDENTIAL_SERVICE,
/* isMultipleMode= */ true),
null,
PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
mContext = context;
}
Key details:
| Parameter | Purpose |
|---|---|
Settings.Secure.CREDENTIAL_SERVICE |
The setting key storing enabled provider component names |
isMultipleMode=true |
Allows multiple concurrent providers (unlike autofill's single-provider model) |
PACKAGE_UPDATE_POLICY_REFRESH_EAGER |
Eagerly rebuilds provider list when packages change |
Enabled providers are stored as a colon-separated list of flattened ComponentName
strings in Settings.Secure.CREDENTIAL_SERVICE. A separate setting,
Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, tracks which providers are "primary"
(preferred for credential creation).
41.2.3 System vs. User-Configurable Providers¶
The service maintains two categories of providers:
graph LR
subgraph "Per-User Provider Lists"
UC["User-Configurable Providers<br/>from Settings.Secure.CREDENTIAL_SERVICE"]
SP["System Providers<br/>discovered via SYSTEM_SERVICE_INTERFACE"]
end
UC --> CONCAT[Concatenated List]
SP --> CONCAT
CONCAT --> RS[Used for Request Sessions]
User-configurable providers are those the user has explicitly enabled in Settings.
They declare the standard CredentialProviderService.SERVICE_INTERFACE intent filter.
System providers are OEM-installed providers that declare the
CredentialProviderService.SYSTEM_SERVICE_INTERFACE intent filter. They are
always available regardless of user settings. The system discovers them via:
// From CredentialManagerService.java
private List<CredentialManagerServiceImpl> constructSystemServiceListLocked(
int resolvedUserId) {
List<CredentialProviderInfo> serviceInfos =
CredentialProviderInfoFactory.getAvailableSystemServices(
mContext, resolvedUserId,
/* disableSystemAppVerificationForTests= */ false,
new HashSet<>());
// ... wrap each in CredentialManagerServiceImpl
}
41.2.4 Request Session Management¶
All ongoing credential operations are tracked per-user through request sessions:
// From CredentialManagerService.java
@GuardedBy("mLock")
private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions =
new SparseArray<>();
The SparseArray is keyed by user ID. Each user can have multiple concurrent
request sessions (identified by IBinder tokens). Sessions are added when a
request begins and removed when they complete or are cancelled:
private void addSessionLocked(int userId, RequestSession session) {
synchronized (mLock) {
Map<IBinder, RequestSession> sessions = mRequestSessions.get(userId);
if (sessions == null) {
sessions = new HashMap<>();
mRequestSessions.put(userId, sessions);
}
sessions.put(session.mRequestId, session);
}
}
41.2.5 The CredentialManagerServiceStub (Binder Interface)¶
The inner class CredentialManagerServiceStub implements ICredentialManager.Stub
and provides the actual Binder entry points. The main operations are:
| Method | Purpose |
|---|---|
executeGetCredential() |
Retrieve an existing credential (password, passkey) |
executeCreateCredential() |
Create/save a new credential |
executePrepareGetCredential() |
Two-step get: prepare first, then retrieve |
getCandidateCredentials() |
Used by autofill to get candidate credentials |
clearCredentialState() |
Clear provider-side state (e.g., on sign-out) |
setEnabledProviders() |
Configure which providers are active |
getCredentialProviderServices() |
List available providers |
isEnabledCredentialProviderService() |
Check if a specific provider is enabled |
registerCredentialDescription() |
Register credential descriptions for matching |
41.2.6 Get Credential Flow (Detailed)¶
The executeGetCredential() method orchestrates the complete get flow:
sequenceDiagram
participant Client as Client App
participant Stub as CredentialManagerServiceStub
participant GRS as GetRequestSession
participant CMSI as CredentialManagerServiceImpl
participant PGS as ProviderGetSession
participant RCS as RemoteCredentialService
participant Prov as CredentialProviderService
participant UI as Selector UI
Client->>Stub: executeGetCredential(request, callback)
Note over Stub: Validate request, create session
Stub->>GRS: new GetRequestSession(...)
Stub->>CMSI: initiateProviderSessionForRequestLocked()
CMSI->>PGS: createNewSession()
PGS->>RCS: new RemoteCredentialService()
Note over Stub: Invoke all provider sessions
loop For each provider
PGS->>RCS: onBeginGetCredential(beginRequest)
RCS->>Prov: service.onBeginGetCredential()
Prov-->>RCS: BeginGetCredentialResponse
RCS-->>PGS: onProviderResponseSuccess()
PGS-->>GRS: onProviderStatusChanged(CREDENTIALS_RECEIVED)
end
GRS->>GRS: isUiInvocationNeeded()?
GRS->>UI: launchUiWithProviderData()
UI-->>GRS: onUiSelection(entry)
GRS->>PGS: onUiEntrySelected()
Note over PGS: Fire PendingIntent for selected entry
PGS-->>GRS: onFinalResponseReceived()
GRS-->>Client: callback.onResponse(GetCredentialResponse)
Step by step within the code:
-
Request validation and session creation:
// CredentialManagerServiceStub.executeGetCredential() final GetRequestSession session = new GetRequestSession( getContext(), mSessionManager, mLock, userId, callingUid, callback, request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), getEnabledProvidersForUser(userId), CancellationSignal.fromTransport(cancelTransport), timestampBegan); addSessionLocked(userId, session); -
Provider session initiation: The service iterates over all enabled providers and creates a
ProviderGetSessionfor each that is capable of handling the request: -
Provider invocation: Each
ProviderSession.invokeSession()binds to the remote provider and callsonBeginGetCredential. -
Response aggregation: As each provider responds,
onProviderStatusChanged()is called. When all providers have responded and at least one has credentials: -
UI display and user selection: The system UI presents the aggregated credentials. On selection,
onUiSelection()routes to the appropriateProviderSession. -
Final credential delivery: The provider's
PendingIntentresolves the full credential, which flows back throughonFinalResponseReceived()to the client.
41.2.7 Create Credential Flow¶
The create flow follows a similar pattern but uses CreateRequestSession and
ProviderCreateSession:
sequenceDiagram
participant App as Client App
participant CMS as CredentialManagerService
participant CRS as CreateRequestSession
participant Prov as CredentialProviderService
participant UI as Selector UI
App->>CMS: createCredential(request)
CMS->>CRS: new CreateRequestSession(...)
loop For each enabled provider
CMS->>Prov: onBeginCreateCredential(beginRequest)
Prov-->>CMS: BeginCreateCredentialResponse<br/>(CreateEntry items)
end
CRS->>UI: Show create entries
UI-->>CRS: User selects provider
Note over CRS: Fire PendingIntent
Prov-->>CRS: CreateCredentialResponse
CRS-->>App: callback.onResponse()
The create flow differs in that:
- Only one credential is being created (not selecting from multiple)
- The response contains
CreateEntryitems, one per provider willing to save - Primary providers are highlighted in the UI, determined by
Settings.Secure.CREDENTIAL_SERVICE_PRIMARY
41.2.8 Permission Model¶
The Credential Manager enforces several permissions:
| Permission | Required For |
|---|---|
CREDENTIAL_MANAGER_SET_ORIGIN |
Setting a custom origin (for browsers making cross-origin requests) |
CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS |
Restricting which providers can respond |
WRITE_SECURE_SETTINGS |
Configuring enabled providers via setEnabledProviders() |
QUERY_ALL_PACKAGES or LIST_ENABLED_CREDENTIAL_PROVIDERS |
Listing available providers |
PROVIDE_REMOTE_CREDENTIALS |
Offering remote/hybrid entries (OEM-only) |
The origin is critical for WebAuthn/passkey operations where a browser acts on behalf of a web application. Only privileged callers (typically the browser with appropriate permissions) can set the origin, which providers use to verify the relying party.
41.2.9 Package Lifecycle Handling¶
When a provider package is updated or removed, the service reacts:
// CredentialManagerService.handlePackageRemovedMultiModeLocked()
protected void handlePackageRemovedMultiModeLocked(String packageName, int userId) {
updateProvidersWhenPackageRemoved(new SettingsWrapper(mContext), packageName, userId);
// Remove from user-configurable services cache
// Remove from system services cache
// Evict from CredentialDescriptionRegistry
}
For package updates, CredentialManagerServiceImpl.handlePackageUpdateLocked()
re-validates the provider's manifest and capabilities.
41.3 Credential Providers¶
41.3.1 The CredentialProviderService Contract¶
A credential provider implements CredentialProviderService, an abstract Service
class. Providers must handle three callback categories:
classDiagram
class CredentialProviderService {
<<abstract>>
+onBeginGetCredential(request, cancellation, callback)*
+onBeginCreateCredential(request, cancellation, callback)*
+onClearCredentialState(request, cancellation, callback)*
}
class PasswordManager {
+onBeginGetCredential()
+onBeginCreateCredential()
+onClearCredentialState()
}
class PasskeyProvider {
+onBeginGetCredential()
+onBeginCreateCredential()
+onClearCredentialState()
}
CredentialProviderService <|-- PasswordManager
CredentialProviderService <|-- PasskeyProvider
Source: frameworks/base/core/java/android/service/credentials/CredentialProviderService.java
41.3.2 Manifest Declaration¶
Providers register through AndroidManifest.xml:
<service
android:name=".MyCredentialProvider"
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
android:exported="true">
<!-- Standard provider interface (user-configurable) -->
<intent-filter>
<action android:name="android.service.credentials.CredentialProviderService" />
</intent-filter>
<!-- Declare supported credential types in metadata -->
<meta-data
android:name="android.credentials.provider"
android:resource="@xml/provider_config" />
</service>
The metadata XML declares supported credential types:
<!-- res/xml/provider_config.xml -->
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
<capabilities>
<capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>
System providers use a different intent filter action:
41.3.3 Service Capability Checking¶
When initiating provider sessions, the system checks whether each provider supports the requested credential types:
// From CredentialManagerServiceImpl.java
@GuardedBy("mLock")
boolean isServiceCapableLocked(List<String> requestedOptions) {
if (mInfo == null) {
return false;
}
for (String capability : requestedOptions) {
if (mInfo.hasCapability(capability)) {
return true;
}
}
return false;
}
Only providers with matching capabilities are included in a request session. This prevents sending password requests to passkey-only providers and vice versa.
41.3.4 BeginGetCredentialRequest and Response¶
The "begin" phase request contains:
classDiagram
class BeginGetCredentialRequest {
-callingAppInfo : CallingAppInfo
-beginGetCredentialOptions : List~BeginGetCredentialOption~
}
class BeginGetCredentialOption {
-id : String
-type : String
-candidateQueryData : Bundle
}
class CallingAppInfo {
-packageName : String
-signingInfo : SigningInfo
-origin : String
}
BeginGetCredentialRequest --> CallingAppInfo
BeginGetCredentialRequest --> "*" BeginGetCredentialOption
The response describes what the provider can offer:
classDiagram
class BeginGetCredentialResponse {
-credentialEntries : List~CredentialEntry~
-actions : List~Action~
-authenticationActions : List~Action~
-remoteEntry : RemoteEntry
}
class CredentialEntry {
-key : String
-subkey : String
-pendingIntent : PendingIntent
-slice : Slice
}
class Action {
-title : CharSequence
-pendingIntent : PendingIntent
}
class RemoteEntry {
-pendingIntent : PendingIntent
}
BeginGetCredentialResponse --> "*" CredentialEntry
BeginGetCredentialResponse --> "*" Action
BeginGetCredentialResponse --> RemoteEntry
CredentialEntry -- Represents a single available credential (e.g., "user@example.com
password" or "Passkey for example.com"). Contains a PendingIntent that fires when
selected.
Action -- A generic action the provider wants to show (e.g., "Manage passwords").
Authentication Action -- Shown when the provider's vault is locked. Selecting it
launches the provider's unlock flow. After unlocking, the provider returns the actual
BeginGetCredentialResponse through EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE.
RemoteEntry -- For hybrid/cross-device flows. Only honored from the OEM-configured hybrid service, checked via:
// From ProviderSession.java
protected boolean enforceRemoteEntryRestrictions(
@Nullable ComponentName expectedRemoteEntryProviderService) {
if (!mComponentName.equals(expectedRemoteEntryProviderService)) {
Slog.w(TAG, "Remote entry being dropped as it is not from the service "
+ "configured by the OEM.");
return false;
}
// Also verify PROVIDE_REMOTE_CREDENTIALS permission
}
41.3.5 RemoteCredentialService Connection¶
RemoteCredentialService extends ServiceConnector.Impl to manage the binding
lifecycle with each provider:
// From RemoteCredentialService.java
public class RemoteCredentialService
extends ServiceConnector.Impl<ICredentialProviderService> {
private static final long TIMEOUT_REQUEST_MILLIS = 3 * DateUtils.SECOND_IN_MILLIS;
private static final long TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS =
5 * DateUtils.SECOND_IN_MILLIS;
}
Key timeouts:
- Request timeout: 3 seconds. If a provider does not respond within 3 seconds, the request is cancelled and the provider is reported as failed.
- Idle disconnect: 5 seconds. After completing requests, the service unbinds after 5 seconds of inactivity.
The connection uses CompletableFuture with orTimeout():
// From RemoteCredentialService.onBeginGetCredential()
CompletableFuture<BeginGetCredentialResponse> connectThenExecute =
postAsync(service -> {
CompletableFuture<BeginGetCredentialResponse> getCredentials =
new CompletableFuture<>();
service.onBeginGetCredential(request, new IBeginGetCredentialCallback.Stub() {
@Override
public void onSuccess(BeginGetCredentialResponse response) {
getCredentials.complete(response);
}
@Override
public void onFailure(String errorType, CharSequence message) {
getCredentials.completeExceptionally(
new GetCredentialException(errorType, errorMsg));
}
// ...
});
return getCredentials;
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
Error handling covers several failure modes:
| Error | Constant | Handling |
|---|---|---|
| Provider timeout | ERROR_TIMEOUT |
Cancellation signal dispatched, provider marked failed |
| Provider exception | ERROR_PROVIDER_FAILURE |
Exception propagated to session |
| Task cancelled | ERROR_TASK_CANCELED |
Cancellation acknowledged |
| Binder death | binderDied() |
onProviderServiceDied() callback invoked |
| Unknown error | ERROR_UNKNOWN |
Generic failure reported |
41.3.6 ProviderSession State Machine¶
Each ProviderSession tracks its lifecycle through a state machine:
stateDiagram-v2
[*] --> NOT_STARTED
NOT_STARTED --> PENDING : invokeSession
PENDING --> CREDENTIALS_RECEIVED : onProviderResponseSuccess<br/>has credentials
PENDING --> SAVE_ENTRIES_RECEIVED : onProviderResponseSuccess<br/>create flow
PENDING --> EMPTY_RESPONSE : onProviderResponseSuccess<br/>no credentials
PENDING --> CANCELED : cancelProviderRemoteSession
PENDING --> SERVICE_DEAD : onProviderServiceDied
CREDENTIALS_RECEIVED --> COMPLETE : onUiEntrySelected<br/>final response
CREDENTIALS_RECEIVED --> NO_CREDENTIALS_FROM_AUTH_ENTRY : auth entry empty
SAVE_ENTRIES_RECEIVED --> COMPLETE : onUiEntrySelected
COMPLETE --> [*]
CANCELED --> [*]
SERVICE_DEAD --> [*]
EMPTY_RESPONSE --> [*]
The status checks are used to decide when to invoke the UI:
// From ProviderSession.java
public static boolean isUiInvokingStatus(Status status) {
return status == Status.CREDENTIALS_RECEIVED
|| status == Status.SAVE_ENTRIES_RECEIVED
|| status == Status.NO_CREDENTIALS_FROM_AUTH_ENTRY;
}
public static boolean isStatusWaitingForRemoteResponse(Status status) {
return status == Status.PENDING;
}
The RequestSession waits until no provider is in PENDING state before deciding
whether to show the UI or report an error.
41.3.7 Metrics Collection¶
The credential framework includes extensive telemetry. Every session tracks:
| Metric | Collector |
|---|---|
| API call timestamps | RequestSessionMetric |
| Per-provider candidate phase | CandidatePhaseMetric |
| UI invocation timing | RequestSessionMetric.collectUiCallStartTime() |
| Chosen provider status | ChosenProviderFinalPhaseMetric |
| Authentication entry usage | BrowsedAuthenticationMetric |
| Credential type selected | collectChosenClassType() |
Metric classes reside in:
frameworks/base/services/credentials/java/com/android/server/credentials/metrics/
41.4 Passkeys and FIDO2¶
41.4.1 What Are Passkeys?¶
Passkeys are FIDO2/WebAuthn credentials based on public-key cryptography. Unlike passwords:
| Property | Password | Passkey |
|---|---|---|
| Secret storage | Server stores hash | Server stores public key only |
| Phishing resistance | None | Origin-bound |
| Replay attacks | Possible | Challenge-response prevents |
| User experience | Must remember | Biometric/device unlock |
| Cross-device | Manual entry | QR code or Bluetooth hybrid |
A passkey consists of:
- A private key stored securely on the device (in a credential provider)
- A public key registered with the relying party (website/app)
- A credential ID linking the two
41.4.2 Passkey Creation (Registration)¶
sequenceDiagram
participant RP as Relying Party (Server)
participant App as Client App
participant CM as CredentialManager
participant Prov as Passkey Provider
RP->>App: Registration challenge + options
App->>CM: createCredential(CreatePublicKeyCredentialRequest)
Note over CM: type = "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
CM->>Prov: onBeginCreateCredential(beginRequest)
Prov-->>CM: BeginCreateCredentialResponse(CreateEntry)
Note over CM: User selects provider in UI
CM->>Prov: PendingIntent → provider Activity
Note over Prov: Generate key pair<br/>Sign challenge with private key<br/>Store private key securely
Prov-->>CM: CreateCredentialResponse(attestation)
CM-->>App: CreateCredentialResponse
App->>RP: Send attestation for verification
The CreatePublicKeyCredentialRequest carries a JSON string conforming to the
WebAuthn PublicKeyCredentialCreationOptions spec:
{
"rp": { "id": "example.com", "name": "Example" },
"user": { "id": "base64userId", "name": "user@example.com" },
"challenge": "base64challenge",
"pubKeyCredParams": [
{ "type": "public-key", "alg": -7 },
{ "type": "public-key", "alg": -257 }
],
"authenticatorSelection": {
"residentKey": "required",
"userVerification": "required"
}
}
Algorithm identifiers follow the COSE algorithm registry:
-7(ES256): ECDSA with P-256 and SHA-256-257(RS256): RSASSA-PKCS1-v1_5 with SHA-256
41.4.3 Passkey Authentication (Assertion)¶
sequenceDiagram
participant RP as Relying Party
participant App as Client App
participant CM as CredentialManager
participant Prov as Passkey Provider
RP->>App: Authentication challenge
App->>CM: getCredential(GetPublicKeyCredentialOption)
CM->>Prov: onBeginGetCredential(beginRequest)
Note over Prov: Search for matching passkeys<br/>(by rpId)
Prov-->>CM: CredentialEntry per matching passkey
Note over CM: User selects passkey + biometric
CM->>Prov: PendingIntent → provider Activity
Note over Prov: Sign challenge with private key
Prov-->>CM: GetCredentialResponse(assertion)
CM-->>App: GetCredentialResponse
App->>RP: Send assertion for verification
The GetPublicKeyCredentialOption contains a JSON string conforming to
PublicKeyCredentialRequestOptions:
{
"rpId": "example.com",
"challenge": "base64challenge",
"allowCredentials": [],
"userVerification": "required"
}
An empty allowCredentials array means "discoverable credentials" (passkeys), where
the provider searches its store for any passkeys matching the relying party ID.
41.4.4 Origin Verification¶
For passkeys to provide phishing resistance, the origin must be verified. When a browser initiates a passkey operation, it sets the origin:
// Browser sets origin for web-initiated requests
GetCredentialRequest request = new GetCredentialRequest.Builder()
.addCredentialOption(publicKeyOption)
.setOrigin("https://example.com") // Requires CREDENTIAL_MANAGER_SET_ORIGIN
.build();
The CallingAppInfo passed to providers includes:
- Package name of the calling app
- Signing info (certificates) of the calling app
- Origin string (if set by a privileged caller)
Providers verify the origin matches the relying party's expected origin and that the calling app's signing certificate matches the Digital Asset Links declarations.
41.4.5 Hybrid / Cross-Device Authentication¶
Cross-device passkey authentication (using a phone to sign in on a laptop) uses the FIDO2 hybrid transport. In the Credential Manager model:
- The OEM configures a hybrid service via
config_defaultCredentialManagerHybridService - That service can include a
RemoteEntryin itsBeginGetCredentialResponse - When the user selects the remote entry, the hybrid flow begins (typically via BLE + CTAP2)
The hybrid service is validated through:
// From RequestSession.java constructor
mHybridService = context.getResources().getString(
R.string.config_defaultCredentialManagerHybridService);
Only the OEM-designated service, verified through enforceRemoteEntryRestrictions(),
can offer remote entries. This prevents arbitrary apps from intercepting cross-device
authentication flows.
41.4.6 Attestation¶
During passkey creation, the provider may generate an attestation statement proving
the key was created in specific hardware (e.g., StrongBox, TEE). The attestation
data is included in the CreateCredentialResponse and forwarded to the relying party.
Common attestation formats:
- None: No attestation; provider self-signs
- Packed: Compact attestation format
- Android Key Attestation: Uses Android's hardware-backed key attestation chain
- TPM: Trusted Platform Module attestation (rare on Android)
41.5 Password and Autofill Integration¶
41.5.1 Password Credentials¶
Password credentials use the type Credential.TYPE_PASSWORD_CREDENTIAL. The data
bundle contains:
| Key | Type | Description |
|---|---|---|
android.credentials.BUNDLE_KEY_ID |
String | Username/identifier |
android.credentials.BUNDLE_KEY_PASSWORD |
String | Password value |
A password-focused BeginGetCredentialResponse returns CredentialEntry items,
one for each stored password matching the calling app.
41.5.2 Autofill Bridge¶
The Credential Manager integrates with the existing autofill framework through a
specialized code path. The getCandidateCredentials() Binder method is restricted
to the system's configured credential-autofill service:
// From CredentialManagerServiceStub.getCandidateCredentials()
String credentialManagerAutofillCompName = mContext.getResources().getString(
R.string.config_defaultCredentialManagerAutofillService);
ComponentName componentName = ComponentName.unflattenFromString(
credentialManagerAutofillCompName);
// Verify the caller IS this configured autofill service
PackageManager pm = mContext.createContextAsUser(
UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
String callingProcessPackage = pm.getNameForUid(callingUid);
if (!Objects.equals(componentName.getPackageName(), callingProcessPackage)) {
throw new SecurityException(callingProcessPackage
+ " is not the device's credential autofill package.");
}
This creates a GetCandidateRequestSession which returns candidates to the autofill
service for display in the autofill dropdown, providing a seamless experience in
form fields.
41.5.3 Autofill Placeholder¶
When a credential-only provider (one without an autofill service component) is set as primary, the system stores a placeholder value:
// From CredentialManagerService.java
public static final String AUTOFILL_PLACEHOLDER_VALUE = "credential-provider";
This tells the autofill framework that credential management is handled by the
Credential Manager rather than a traditional AutofillService.
41.5.4 Migration Path¶
The Credential Manager provides a migration path from legacy autofill providers:
graph TB
subgraph "Legacy (Android 13 and below)"
AF[AutofillService]
AM[AccountManager]
FIDO[FIDO2 SDK]
end
subgraph "Modern (Android 14+)"
CPS[CredentialProviderService]
CM[CredentialManager API]
end
AF -.->|"Can also implement"| CPS
AM -.->|"Replaced by"| CM
FIDO -.->|"Replaced by"| CM
CPS --> CM
A single provider app can implement both AutofillService (for backward
compatibility) and CredentialProviderService (for the modern flow). The system
coordinates between them through the autofill bridge.
41.6 Digital Credentials¶
41.6.1 Identity Documents¶
Android's Credential Manager has been extended to support digital identity documents -- government-issued IDs, driving licenses, health insurance cards, and other verifiable credentials. These use the digital credential type system.
The framework provides a generic container; the actual credential format (mDL per ISO 18013-5, W3C Verifiable Credentials, etc.) is handled by the provider.
41.6.2 Credential Description Registry¶
The CredentialDescriptionRegistry is a per-user, in-memory registry where providers
pre-register descriptions of their available credentials. This enables the system
to route requests to appropriate providers without querying every provider:
// From CredentialDescriptionRegistry.java
public class CredentialDescriptionRegistry {
private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16;
private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
private int mTotalDescriptionCount;
}
Source: frameworks/base/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
The registry is:
- Per-user: Each user has an independent instance via
SparseArray - In-memory: Not persisted across reboots; providers re-register on startup
- Size-limited: Maximum 128 total descriptions, 16 per provider
41.6.3 Registration and Matching¶
Providers register credential descriptions during startup or when their credential inventory changes:
// Provider registers a digital ID credential
CredentialDescription description = new CredentialDescription(
"com.credman.IdentityCredential",
Set.of("org.iso.18013.5.1.family_name",
"org.iso.18013.5.1.given_name",
"org.iso.18013.5.1.portrait"),
credentialEntries);
RegisterCredentialDescriptionRequest request =
new RegisterCredentialDescriptionRequest(Set.of(description));
credentialManager.registerCredentialDescription(request);
When a get request arrives with SUPPORTED_ELEMENT_KEYS, the system uses the
registry to find matching providers:
// From CredentialDescriptionRegistry.java
public Set<FilterResult> getMatchingProviders(Set<Set<String>> supportedElementKeys) {
Set<FilterResult> result = new HashSet<>();
for (String packageName : mCredentialDescriptions.keySet()) {
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
for (CredentialDescription containedDescription : currentSet) {
if (canProviderSatisfyAny(
containedDescription.getSupportedElementKeys(),
supportedElementKeys)) {
result.add(new FilterResult(packageName,
containedDescription.getSupportedElementKeys(),
containedDescription.getCredentialEntries()));
}
}
}
return result;
}
Matching uses set containment -- a provider matches if its registered element keys are a superset of the requested element keys:
static boolean checkForMatch(Set<String> registeredElementKeys,
Set<String> requestedElementKeys) {
return registeredElementKeys.containsAll(requestedElementKeys);
}
41.6.4 Registry-Based Get Flow¶
When a get request includes credential description options, the system takes a
different path through prepareProviderSessions():
graph TB
REQ[GetCredentialRequest]
REQ --> SPLIT{Has SUPPORTED_ELEMENT_KEYS?}
SPLIT -->|Yes| REG["Registry Path<br/>ProviderRegistryGetSession"]
SPLIT -->|No| REMOTE["Remote Service Path<br/>ProviderGetSession"]
REG --> FILTER["CredentialDescriptionRegistry<br/>getMatchingProviders"]
FILTER --> SESSIONS["Create sessions for<br/>matching providers only"]
REMOTE --> ALL["Create sessions for<br/>all enabled providers"]
SESSIONS --> MERGE[Merge all sessions]
ALL --> MERGE
MERGE --> UI[UI with combined results]
This optimization avoids binding to providers that cannot possibly have matching credentials, reducing latency for digital credential requests.
41.6.5 Verifiable Presentations¶
For digital credential use cases, the flow typically involves:
- Verifier requests specific claims (e.g., "prove you are over 21")
- App creates a
GetCredentialRequestwith element keys describing needed claims - System routes to providers via the registry
- Provider presents user consent UI showing what will be shared
- User approves selective disclosure
- Provider generates a cryptographically signed presentation
- Response flows back through the system to the verifier
The system never sees the actual credential data; it only facilitates the connection between verifier and provider.
41.7 Try It¶
41.7.1 Inspecting Credential Manager State¶
List enabled credential providers:
# Check the Settings.Secure value for the current user
adb shell settings get --user 0 secure credential_service
# Check primary providers
adb shell settings get --user 0 secure credential_service_primary
Dump CredentialManagerService state:
This shows:
- Active provider services (user-configurable and system)
- Ongoing request sessions
- Provider capability information
- Service binding states
41.7.2 Enabling a Provider¶
# Set a provider as enabled (requires WRITE_SECURE_SETTINGS)
adb shell settings put --user 0 secure credential_service \
"com.example.myprovider/.MyCredentialProviderService"
# Set a provider as primary
adb shell settings put --user 0 secure credential_service_primary \
"com.example.myprovider/.MyCredentialProviderService"
41.7.3 Implementing a Minimal Provider¶
A basic password provider demonstrates the two-phase protocol.
1. Service declaration (AndroidManifest.xml):
<service
android:name=".DemoCredentialProvider"
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.service.credentials.CredentialProviderService" />
</intent-filter>
<meta-data
android:name="android.credentials.provider"
android:resource="@xml/provider_config" />
</service>
2. Provider configuration (res/xml/provider_config.xml):
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
<capabilities>
<capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
</capabilities>
</credential-provider>
3. Service implementation:
class DemoCredentialProvider : CredentialProviderService() {
override fun onBeginGetCredential(
request: BeginGetCredentialRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<BeginGetCredentialResponse,
GetCredentialException>
) {
val entries = mutableListOf<CredentialEntry>()
for (option in request.beginGetCredentialOptions) {
if (option.type == Credential.TYPE_PASSWORD_CREDENTIAL) {
// Look up stored credentials for the calling app
val stored = lookupPasswords(request.callingAppInfo.packageName)
for (cred in stored) {
entries.add(
CredentialEntry.Builder(
option.id,
createPendingIntent(cred.id)
)
.build()
)
}
}
}
callback.onResult(
BeginGetCredentialResponse.Builder()
.setCredentialEntries(entries)
.build()
)
}
override fun onBeginCreateCredential(
request: BeginCreateCredentialRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<BeginCreateCredentialResponse,
CreateCredentialException>
) {
callback.onResult(
BeginCreateCredentialResponse.Builder()
.addCreateEntry(
CreateEntry.Builder("Save to Demo Provider",
createSavePendingIntent()
).build()
).build()
)
}
override fun onClearCredentialState(
request: ClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void, ClearCredentialStateException>
) {
// Clear any cached credential state
callback.onResult(null)
}
}
4. Client usage:
val credentialManager = getSystemService(CredentialManager::class.java)
// Get a credential
val getRequest = GetCredentialRequest.Builder()
.addCredentialOption(
GetPasswordOption()
)
.build()
credentialManager.getCredential(
context = this,
request = getRequest,
cancellationSignal = null,
executor = mainExecutor,
callback = object : OutcomeReceiver<GetCredentialResponse,
GetCredentialException> {
override fun onResult(result: GetCredentialResponse) {
// Handle credential: result.credential.data
}
override fun onError(error: GetCredentialException) {
// Handle error
}
}
)
41.7.4 Debugging Provider Communication¶
Enable verbose logging:
Monitor provider binding:
Check for timeout issues:
# The 3-second timeout is logged when providers are slow
adb logcat | grep "Remote provider response timed"
41.7.5 Credential Description API¶
Check if the description API is enabled:
Enable it for testing:
41.7.6 Testing Passkey Flows¶
To test passkey creation and authentication:
- Set up a WebAuthn relying party (or use webauthn.io for testing)
- Enable a passkey-capable provider (e.g., Google Password Manager)
- In a test app or browser:
// Create a passkey
val createRequest = CreateCredentialRequest(
"androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
Bundle().apply {
putString(
"androidx.credentials.BUNDLE_KEY_REQUEST_JSON",
"""{"rp":{"id":"example.com","name":"Example"},
"user":{"id":"dXNlcg","name":"user@example.com"},
"challenge":"Y2hhbGxlbmdl",
"pubKeyCredParams":[{"type":"public-key","alg":-7}],
"authenticatorSelection":{"residentKey":"required"}}"""
)
}
)
credentialManager.createCredential(context, createRequest, ...)
41.7.7 DeviceConfig Flags¶
The Credential Manager respects several DeviceConfig flags:
| Flag | Namespace | Purpose |
|---|---|---|
enable_credential_manager |
credential |
Master enable/disable |
enable_credential_description_api |
credential |
Enable registry-based matching |
# Check if Credential Manager is enabled
adb shell device_config get credential enable_credential_manager
# Disable for testing
adb shell device_config put credential enable_credential_manager false
41.7.8 Sequence of Key Log Messages¶
When tracing a complete get-credential flow, look for these log messages in order:
CredentialManager: starting executeGetCredential with callingPackage: com.example.app
CredentialManager: CredentialManagerServiceImpl constructed for: com.provider/.Service
CredentialManager: Provider session created and being added for: com.provider/.Service
CredentialManager: Status changed for: com.provider/.Service, with status: CREDENTIALS_RECEIVED
CredentialManager: Provider status changed - ui invocation is needed
CredentialManager: For ui, provider data size: 1
CredentialManager: onFinalResponseReceived from: com.provider/.Service
CredentialManager: finishing session with propagateCancellation false
Summary¶
The Credential Manager framework transforms Android's credential handling from a fragmented collection of APIs into a unified, secure, and extensible system. Its architecture rests on several key pillars:
CredentialManagerServiceorchestrates the entire flow, managing per-user provider instances and request sessions- The two-phase protocol (begin/finalize) ensures credential material is never unnecessarily loaded or exposed to the system
ProviderSessionstate machines track each provider's progress through a well-defined lifecycleRemoteCredentialServicehandles the asynchronous binding and communication with provider processes, with strict timeouts- The
CredentialDescriptionRegistryenables efficient routing for digital credential use cases - System-mediated UI via
CredentialManagerUiensures users always see a trustworthy credential picker
The framework supports passwords, passkeys (FIDO2/WebAuthn), and digital identity credentials through the same unified path, with extensibility for future credential types through the provider capability system.
Appendix: Deep Dive into Internal Classes¶
A.1 CredentialManagerUi Internals¶
The CredentialManagerUi class manages the bridge between system_server and the
credential selector UI (typically implemented in SystemUI or a dedicated selector app).
Source: frameworks/base/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
The UI operates through a ResultReceiver pattern:
// From CredentialManagerUi.java
@NonNull
private final ResultReceiver mResultReceiver = new ResultReceiver(
new Handler(Looper.getMainLooper())) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
handleUiResult(resultCode, resultData);
}
};
Result codes from the UI:
| Result Code | Constant | Handling |
|---|---|---|
RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION |
User selected a credential | Route to ProviderSession.onUiEntrySelected() |
RESULT_CODE_DIALOG_USER_CANCELED |
User dismissed the dialog | Call onUiCancellation(true) |
RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS |
User went to settings | Call onUiCancellation(false) |
RESULT_CODE_DATA_PARSING_FAILURE |
UI failed to parse data | Call onUiSelectorInvocationFailure() |
The UI status tracking prevents duplicate operations:
// From CredentialManagerUi.java
enum UiStatus {
IN_PROGRESS, // Waiting for provider responses
USER_INTERACTION, // UI is displayed, user interacting
NOT_STARTED, // Initial state
TERMINATED // UI dismissed or failed
}
The createPendingIntent() method constructs the intent for the selector Activity.
It packages:
RequestInfodescribing what is being requestedProviderDatafrom all responding providersResultReceiverfor receiving the selection result- Session tracking IDs for metrics
A.2 ProviderGetSession Details¶
ProviderGetSession is the concrete implementation that handles the get-credential
provider communication. It creates the BeginGetCredentialRequest from the client's
GetCredentialRequest:
graph LR
subgraph "Client Request"
GCR[GetCredentialRequest]
CO1["CredentialOption 1<br/>type: PASSWORD"]
CO2["CredentialOption 2<br/>type: PUBLIC_KEY"]
GCR --> CO1
GCR --> CO2
end
subgraph "Provider Begin Request"
BGR[BeginGetCredentialRequest]
BGO1["BeginGetCredentialOption 1<br/>type: PASSWORD<br/>candidateQueryData"]
BGO2["BeginGetCredentialOption 2<br/>type: PUBLIC_KEY<br/>candidateQueryData"]
BGR --> BGO1
BGR --> BGO2
end
CO1 -->|Transformed| BGO1
CO2 -->|Transformed| BGO2
Each CredentialOption in the client request is transformed into a
BeginGetCredentialOption for the provider. The transformation strips out
sensitive retrieval data and sends only the candidate query data -- information
the provider needs to search its store.
A.3 ProviderCreateSession Details¶
For credential creation, ProviderCreateSession transforms the
CreateCredentialRequest into a BeginCreateCredentialRequest:
graph LR
subgraph "Client Create Request"
CCR[CreateCredentialRequest]
TYPE[type: PUBLIC_KEY_CREDENTIAL]
DATA["credentialData: Bundle<br/>contains JSON options"]
end
subgraph "Provider Begin Create"
BCR[BeginCreateCredentialRequest]
BCTYPE[type: PUBLIC_KEY_CREDENTIAL]
BCDATA[candidateQueryData: Bundle]
end
CCR --> BCR
TYPE --> BCTYPE
DATA -->|Filtered| BCDATA
The BeginCreateCredentialResponse from providers contains CreateEntry items.
Each CreateEntry has:
- A display title (e.g., "Save to Google Password Manager")
- A
PendingIntentfor the actual save flow - Optional metadata about the provider
A.4 ClearRequestSession¶
The clear credential state flow is simpler -- it asks all providers to clear any cached state for the calling app:
// From ClearRequestSession.java
// Sends ClearCredentialStateRequest to all providers
// Useful when user signs out of an app
// Providers clear cached tokens, session state, etc.
This operation is critical for security hygiene -- when a user logs out of
an app, the app should call clearCredentialState() to ensure that credential
providers do not have stale authentication state.
A.5 Settings Integration¶
The enabled provider list is stored in Secure Settings, one per user:
Settings.Secure.CREDENTIAL_SERVICE = "credential_service"
Settings.Secure.CREDENTIAL_SERVICE_PRIMARY = "credential_service_primary"
The format is colon-separated flattened ComponentNames:
Primary providers get preferential placement in the creation UI. The system reads these through:
// From CredentialManagerService.java
static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) {
SecureSettingsServiceNameResolver resolver = new SecureSettingsServiceNameResolver(
context, Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
/* isMultipleMode= */ true);
String[] serviceNames = resolver.readServiceNameList(resolvedUserId);
// Parse into ComponentName set
}
A.6 Error Taxonomy¶
The Credential Manager defines a structured error taxonomy:
Get Credential Errors (GetCredentialException):
| Type | Meaning |
|---|---|
TYPE_NO_CREDENTIAL |
No matching credentials found anywhere |
TYPE_USER_CANCELED |
User dismissed the selector |
TYPE_INTERRUPTED |
UI was interrupted (e.g., by another activity) |
TYPE_UNKNOWN |
Unclassified error |
Create Credential Errors (CreateCredentialException):
| Type | Meaning |
|---|---|
TYPE_NO_CREATE_OPTIONS |
No provider can create the requested type |
TYPE_USER_CANCELED |
User dismissed the creation UI |
TYPE_INTERRUPTED |
UI was interrupted |
TYPE_UNKNOWN |
Unclassified error |
Clear Credential State Errors (ClearCredentialStateException):
| Type | Meaning |
|---|---|
TYPE_UNKNOWN |
Clear operation failed |
Errors flow through the respondToClientWithErrorAndFinish() method in
RequestSession:
// From RequestSession.java
protected void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
// ... status checks
try {
invokeClientCallbackError(errorType, errorMsg);
} catch (RemoteException e) {
Slog.e(TAG, "Issue while responding to client with error : " + e);
}
boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING);
if (isUserCanceled) {
finishSession(false, ApiStatus.USER_CANCELED.getMetricCode());
} else {
finishSession(false, ApiStatus.FAILURE.getMetricCode());
}
}
A.7 Cancellation Architecture¶
Cancellation flows bidirectionally through the stack:
graph TB
subgraph "Client-Initiated Cancellation"
CLIENT["Client calls cancel()"]
CS[CancellationSignal fires]
RS_CANCEL[RequestSession.cancelListener]
UI_CANCEL[Maybe cancel UI]
PROV_CANCEL[Cancel all ProviderSessions]
end
CLIENT --> CS --> RS_CANCEL
RS_CANCEL --> UI_CANCEL
RS_CANCEL --> PROV_CANCEL
subgraph "Provider-Side Cancellation"
PROV_CS[ICancellationSignal from provider]
RCS_DISPATCH[RemoteCredentialService.dispatchCancellationSignal]
PROV_ABORT[Provider aborts operation]
end
PROV_CANCEL --> RCS_DISPATCH --> PROV_ABORT
The client receives an ICancellationSignal transport when calling get or create:
// Return type from executeGetCredential()
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
// Client can call cancelTransport.cancel() at any time
When cancelled:
- The
RequestSession's cancellation listener fires - If the UI is active, a cancel intent is sent to dismiss it
- All pending
ProviderSessioninstances receive cancellation signals - The session terminates with
ApiStatus.CLIENT_CANCELED
A.8 Thread Model¶
The Credential Manager operates on multiple threads:
| Component | Thread | Reason |
|---|---|---|
| Binder calls | Binder thread pool | Incoming IPC from client apps |
| Request session | Main handler | UI callbacks and state management |
| Provider communication | ServiceConnector thread |
Async service binding |
| Result delivery | Main handler | ResultReceiver from UI |
| Metrics logging | Calling thread | Synchronous metric collection |
The RequestSession uses a main-thread Handler:
Provider responses are dispatched to the main thread via:
// From RemoteCredentialService.java
connectThenExecute.whenComplete((result, error) ->
Handler.getMain().post(() -> handleExecutionResponse(result, error, cancellationSink)));
A.9 Feature Flags¶
The Credential Manager uses android.credentials.flags.Flags for feature gating:
// Referenced throughout the codebase:
import android.credentials.flags.Flags;
// Examples:
if (Flags.clearSessionEnabled()) {
// Bind client binder death recipient for session cleanup
}
if (Flags.metricBugfixesContinued()) {
// Apply continued metric bugfixes
}
These flags allow gradual rollout of behavior changes without code branches, following the AOSP trunk-stable development model.
A.10 Security Considerations¶
The Credential Manager enforces several security boundaries:
-
Package identity verification: Every request validates the caller's package name against the Binder calling UID to prevent spoofing:
-
Signing info for origin binding:
CallingAppInfoincludesSigningInfofor asset link verification: -
Provider binding permission: Providers require the
BIND_CREDENTIAL_PROVIDER_SERVICEpermission, preventing unauthorized service binding. -
Origin restriction: Only callers with
CREDENTIAL_MANAGER_SET_ORIGINcan set custom origins, preventing apps from impersonating browsers. -
Binder death detection: Client death is detected and sessions are cleaned up:
-
Remote entry restriction: Only OEM-designated services with the
PROVIDE_REMOTE_CREDENTIALSpermission can offer cross-device entries. -
Per-user isolation: Provider lists and request sessions are strictly per-user, preventing cross-user credential leakage.
A.11 Testing Support¶
The framework includes several testing affordances:
getCredentialProviderServicesForTesting()bypasses system-app verificationCredentialDescriptionRegistry.clearAllSessions()resets state for testsCredentialDescriptionRegistry.setSession()allows injecting test dataisCredentialDescriptionApiEnabled()can be toggled via DeviceConfig
Provider-side testing:
// Use Jetpack Credential Manager test library
testImplementation("androidx.credentials:credentials-testing:1.x.y")
The test library provides fake implementations of the Credential Manager API that can be configured to return specific responses without needing real providers.
A.12 Jetpack Credential Manager vs. Framework API¶
The androidx.credentials Jetpack library wraps the framework API with several
additions:
| Feature | Framework API | Jetpack Library |
|---|---|---|
| Availability | Android 14+ | Android 4.4+ (via Play Services) |
| Passkey type | Raw Bundle | PublicKeyCredential class |
| Password type | Raw Bundle | PasswordCredential class |
| Google Sign-In | Not included | GoogleIdTokenCredential |
| Custom types | Supported | Type-safe wrappers |
| Provider API | CredentialProviderService |
Same |
On Android 14+, the Jetpack library delegates directly to the framework. On older versions, it uses Google Play Services as the backend. This dual-path approach gives developers a single API that works across all Android versions.
A.13 Session Management and Cleanup¶
The SessionManager tracks all active request sessions and ensures cleanup:
// SessionManager implements RequestSession.SessionLifetime
private final SessionManager mSessionManager = new SessionManager();
When a session finishes (success, error, or cancellation), it calls back:
// From RequestSession.java
public interface SessionLifetime {
void onFinishRequestSession(@UserIdInt int userId, IBinder token);
}
This triggers removal from the mRequestSessions map. Without proper cleanup,
abandoned sessions would leak memory and potentially hold references to provider
bindings.
The death recipient mechanism provides an additional safety net:
// From RequestSession.java
private class RequestSessionDeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
Slog.d(TAG, "Client binder died - clearing session");
finishSession(isUiWaitingForData(), ApiStatus.BINDER_DIED.getMetricCode());
}
}
If the client app crashes or is killed, the binder death triggers session cleanup, preventing resource leaks and dangling UI states.
A.14 Provider Response Aggregation Strategy¶
When multiple providers respond to a get request, the system must aggregate and present their results coherently. The aggregation follows these rules:
-
All providers queried in parallel: All enabled providers with matching capabilities receive the
BeginGetCredentialRequestsimultaneously -
Wait for all or timeout: The system waits until all providers respond or the 3-second timeout expires. Any provider that times out is marked as failed
-
Credential entries merged: All credential entries from all providers are combined into a single list for the UI
-
Authentication entries preserved: Each provider's authentication action (for locked vaults) is shown as a separate entry
-
Remote entry deduplicated: Only one remote/hybrid entry is shown (from the OEM-configured service)
-
Primary provider highlighted: If a create request, primary providers get preferential placement
graph TB
subgraph "Provider Responses"
P1["Provider 1<br/>3 password entries"]
P2["Provider 2<br/>1 passkey entry<br/>+ auth action"]
P3["Provider 3<br/>TIMEOUT - no response"]
end
P1 --> AGG[Aggregation]
P2 --> AGG
P3 -.->|Excluded| AGG
AGG --> UI["Credential Selector UI<br/>4 credential entries<br/>1 auth action"]
A.15 The PrepareGetRequestSession¶
The PrepareGetRequestSession supports a two-step retrieval pattern used by the
autofill integration:
- Prepare phase: Query providers and cache the results
- Get phase: Use cached results when the user interacts with a form field
This avoids the latency of querying providers at the moment the user taps a field.
The prepare phase returns a PrepareGetCredentialResponseInternal indicating:
- Whether any credential results are available
- Whether authentication results are available
- Whether remote results are available
- A
PendingIntentto invoke when results are needed
A.16 Provider Information Factory¶
CredentialProviderInfoFactory is responsible for discovering and constructing
provider metadata:
// From CredentialProviderInfoFactory.java (in service.credentials package)
// Discovers providers by querying PackageManager for:
// 1. Services declaring SERVICE_INTERFACE (user-configurable providers)
// 2. Services declaring SYSTEM_SERVICE_INTERFACE (system providers)
// 3. Parses metadata XML for capability declarations
// 4. Checks signing certificates for system provider validation
Factory methods:
getAvailableSystemServices()-- Finds all system providers on the devicegetCredentialProviderServices()-- Gets providers filtered by user preferencescreate()-- Creates aCredentialProviderInfofor a specific component
A.17 Request Types and RequestInfo¶
The RequestInfo class encapsulates the type and parameters of a credential request.
It serves as a key input to the selector UI:
// From RequestInfo.java
public static final String TYPE_GET = "android.credentials.selection.TYPE_GET";
public static final String TYPE_CREATE = "android.credentials.selection.TYPE_CREATE";
public static final String TYPE_GET_VIA_REGISTRY =
"android.credentials.selection.TYPE_GET_VIA_REGISTRY";
| Request Type | Description | Used By |
|---|---|---|
TYPE_GET |
Standard credential retrieval | executeGetCredential() |
TYPE_CREATE |
Credential creation/saving | executeCreateCredential() |
TYPE_GET_VIA_REGISTRY |
Registry-based retrieval (digital creds) | executeGetCredential() with element keys |
The request type determines:
- Which UI layout the selector uses (credential list vs. save prompt)
- Whether primary provider highlighting is applied
- How entries are sorted and presented
A.18 Disabled Provider Data¶
When the selector UI is shown, it may include information about disabled providers:
// From CredentialManagerUi.java
// Disabled providers are shown in the UI to inform users that
// additional credential sources exist but need to be enabled
// The UI may include a "More options" or "Enable provider" action
The DisabledProviderData class carries:
- Provider package name and display name
- An action intent to navigate to provider settings
- The credential types the provider supports
This helps users discover and enable credential providers they have installed but not yet activated.
A.19 Integration with WebView and Browsers¶
For web-based authentication, the passkey flow has special considerations:
sequenceDiagram
participant Web as Web Page
participant WV as WebView/Browser
participant CM as CredentialManager
participant Prov as Provider
Web->>WV: navigator.credentials.create(options)
Note over WV: Parse WebAuthn options
WV->>CM: createCredential(request)<br/>with origin="https://example.com"
Note over CM: Requires CREDENTIAL_MANAGER_SET_ORIGIN
CM->>Prov: onBeginCreateCredential()
Note over Prov: Verify origin matches RP ID
Prov-->>CM: Response
CM-->>WV: CreateCredentialResponse
WV-->>Web: PublicKeyCredential
The browser (or WebView) is responsible for:
- Parsing the JavaScript WebAuthn API call
- Setting the correct origin (requires privileged permission)
- Mapping between W3C WebAuthn types and Android Credential Manager types
- Returning the result in W3C-compliant format to JavaScript
The CallingAppInfo.getOrigin() method provides the web origin that providers
use for relying party verification.
A.20 Credential Manager and Lock Screen¶
The Credential Manager interacts with the lock screen in several ways:
-
Conditional UI: On the lock screen, the system can show credential suggestions in the autofill IME bar without requiring a full app context
-
Biometric gating: Passkey authentication typically requires biometric verification, which providers implement through their PendingIntent Activities
-
Direct boot: In direct boot mode (before CE storage unlock), only DE-stored credentials are accessible. Most credential providers store data in CE storage, so they are unavailable until the user unlocks
-
Credential Manager as keyguard input: Some OEMs integrate passkey authentication directly into the lock screen flow, allowing passkey-based device unlock (though this is not part of AOSP)
A.21 Performance Characteristics¶
Typical timing for credential operations (measured on mid-range device):
| Phase | Duration | Bottleneck |
|---|---|---|
| Session creation | 1-5 ms | Object allocation, lock acquisition |
| Provider binding | 50-200 ms | Service connection establishment |
| Provider query (begin) | 100-500 ms | Provider's credential search |
| UI display | 50-100 ms | Activity launch, layout inflation |
| User selection | Variable | User interaction time |
| Provider finalization | 100-300 ms | Credential retrieval, biometric |
| Total (best case) | ~500 ms | Dominated by provider response |
| Timeout (worst case) | 3000 ms | Provider timeout enforced |
Optimization strategies:
- Pre-warming provider bindings (done by autofill bridge)
PrepareGetRequestSessionfor pre-fetchingCredentialDescriptionRegistryfor skipping non-matching providers- Parallel provider queries (all providers queried simultaneously)