Skip to content
Language: EN

Why does a reader reach another app instead of yours?

Because another service registered the same identifier, and Android resolves the conflict before your code runs: the default wallet app wins, then a single registered service, and otherwise the user is asked. A foreground activity can override the outcome with setPreferredService.

The routing table decides first

The NFC controller holds a routing table: a finite list of rules, each pairing an identifier with a destination, the host processor where apps run or a connected secure element. When a reader sends a SELECT, the controller matches the identifier and sends that command and every command after it to that destination, until another SELECT arrives or the link breaks. Android's card emulation guide describes the table and says that apps never configure it; the platform builds it from the services installed and from what the user has chosen.

Two consequences follow. Partial matches, which ISO/IEC 7816-4 allows, are not supported by Android host card emulation, so an identifier matches whole or not at all. And the table has a size, though the guide requires devices to route the identifiers your app registered to the host.

How Android resolves a conflict

Several services can register the same identifier, and the guide gives the order. The default wallet app wins if it registered the identifier. Failing that, the single service that registered it is invoked. If more than one did, Android asks the user which to use.

The payment category narrows this further. Only one payment group is enabled on the device at a time, and an app that registers payment identifiers transacts only as the wallet role holder, from Android 15, or from the foreground. A scheme that works at one merchant belongs in the other category, whose groups can stay active all the time and which does not compete for that single slot.

An app can ask for the role rather than hope for it: RoleManager.ROLE_WALLET with createRequestRoleIntent, and isRoleHeld to know where it stands.

What a foreground activity changes

setPreferredService names the service to prefer while one activity is resumed, and it overrides the resolution above. The CardEmulation reference asks for it in onResume and for unsetPreferredService in onPause, since the system does not persist the preference: it has to be set again every time the activity resumes.

The call fails in two cases, both about payment. If the service registers payment identifiers and the user does not allow foreground apps to override the default payment service. And if it registers identifiers in the other category that the default payment service also handles, under the same user choice. categoryAllowsForegroundPreference answers before you try.

What to check when a tap goes elsewhere

Ask the platform what it thinks. isDefaultServiceForCategory answers for a category, but the reference warns that it always returns false when the selection mode is always ask or ask if conflict; in those modes, isDefaultServiceForAid is the question that makes sense. getSelectionModeForCategory tells you which mode you are in.

Then look outside your app. Another installed app may register the same identifier, and an off host service may claim it for a secure element, in which case the exchange never reaches any host code at all. Reading the identifiers your service registered, and comparing them with what the reader selects, settles most of these faster than reasoning about them: the APDU page covers the SELECT itself.

Frequently asked questions

Who wins when two apps register the same identifier?

The default wallet app, if it registered it. Otherwise the single service that did, and if several did, Android asks the user.

Why does setPreferredService return false?

The service registers payment identifiers, or identifiers the default payment service also handles, and the user has not allowed foreground apps to override that service.

Does Android match part of an identifier?

No. Partial matches exist in ISO/IEC 7816-4 but Android host card emulation does not support them.

How does an app become the default wallet?

By asking for the wallet role with RoleManager.createRequestRoleIntent, and checking with isRoleHeld. The user grants it.