How does an Android app emulate a card with host card emulation?
With a service that extends HostApduService and answers APDUs, declared in the manifest with the card emulation intent filter, the BIND_NFC_SERVICE permission and an XML file that registers its application identifiers in a category. Android starts the service on a tap, even when the app is closed.
The service and its two methods
Emulation lives in a service, not in an activity. Android's card emulation guide builds the mechanism on HostApduService, which runs in the background: a tap starts it if it is not running, and the transaction happens without the app being opened. Check FEATURE_NFC_HOST_CARD_EMULATION before relying on any of it, and declare it with uses-feature in the manifest.
The class has two methods to implement. processCommandApdu() is called for each APDU the reader sends, and returns the response bytes. It runs on the main thread, so a response that takes work returns null and arrives later through sendResponseApdu() from another thread. onDeactivated() is called when the reader selects an identifier that resolves to another service, or when the radio link breaks, and its argument says which of the two happened.
What travels is an APDU, as the APDU page describes. Android emulates ISO-DEP, ISO/IEC 14443-4, and processes the commands of ISO/IEC 7816-4; the guide mandates ISO-DEP over NFC-A and leaves NFC-B optional. The exchange is half duplex and Android supports a single logical channel, so commands and responses alternate one at a time.
What the manifest and the XML declare
The service declaration carries three additions: an intent filter for the host APDU service action, the android.permission.BIND_NFC_SERVICE permission with android:exported set to true, and a meta data tag pointing to an XML resource. The permission is a system one, so requiring it means that only the operating system can bind to the service. The app also holds the NFC permission.
The XML file is where the identifiers live. Its root, host-apdu-service, carries a description meant for the user and, optionally, requireDeviceUnlock. It contains one or more aid-group tags, each with its own description, a category, and one or more aid-filter tags holding a single identifier each, written in hexadecimal with an even number of characters.
A service that answers from a secure element rather than from your code declares itself the same way, with the off host action and an offhost-apdu-service root. Android has no API for talking to a secure element directly, which the secure element page puts in context.
Identifier groups and categories
An application identifier is up to sixteen bytes, and the reader names it in a SELECT command. The guide recommends registering your own under ISO/IEC 7816-5 when you deploy your own readers, so that it collides with nobody. Identifiers that belong together go in one group, and Android routes a group as a whole: either every identifier in it reaches your service, or none does.
Each group declares a category, payment or other. Only one payment group can be enabled on the device at a time, and an app that registers payment identifiers transacts only when it holds the wallet role, from Android 15, or when it is in the foreground and has called setPreferredService. A closed loop scheme, usable at one merchant, belongs in the other category, where groups can stay active all the time. Which service a reader actually reaches is the subject of the routing page.
What the exchange has to respect
Keep it short. The guide sets a reasonable upper bound at about 1 KB of data, exchangeable in some 300 ms, and that budget decides how long a user has to hold the phone against the reader.
Keep it within what the platform emulates. Host card emulation answers the standard command exchange; a scheme whose security sits below that layer, MIFARE Classic among them, cannot be emulated, as the host card emulation page explains to readers who are not writing the code.
And decide early what happens when the screen is off or the phone is locked, because the answer depends on the Android version and on a device setting rather than on your code alone. The screen off page takes it version by version.
Frequently asked questions
Does the app have to be open for the emulation to work?
No. A tap starts the service if it is not already running, and the exchange happens in the background without any screen of yours.
Can host card emulation emulate a MIFARE Classic card?
No. Android emulates ISO-DEP and processes ISO/IEC 7816-4 commands, and schemes whose security sits below that layer are out of reach.
Why is processCommandApdu called on the main thread?
Because the reader waits for an answer. Return null when the response needs work, then send it with sendResponseApdu from another thread.
What does onDeactivated mean?
Either the reader selected an identifier that resolves to another service, or the radio link broke. The argument says which of the two.