Overview
A standalone application is an application that runs in a device in contact with the end user.
It has at least one local smart card reader and manages itself the interaction with the user.
In the ticketing industry, it is typically the software that runs a validator, a vending machine or a control terminal.
The diagram below illustrates the organization of the local standalone components:
Before you start
- In pre-requisite, read the common concepts page and become familiar with the basic concepts on which Keyple is based.
- Any implementation of a Keyple application starts with the implementation of Keyple Core, please study the workflow proposed in the following chapter.
- Explore the Keyple Core API to discover all the possibilities offered by Keyple Core.
- Take inspiration from the examples.
- Follow the explanations given in the Build your first app section to configure your environment.
- Using the Java components or C++ components pages, import Keyple Core into your project and start playing with Keyple.
- Don’t forget to explore the potential of Keyple card-specific extensions such as Keyple Calypso.
Workflow
Keyple Core is built around the concepts described here and sometimes proposes several ways to perform an action or to achieve a result depending on the needs of the application.
The purpose of this section is to guide you in its use.
Creation of the Smart Card Service
This is the very first step in the realization of a Keyple application:
/* Get the instance of the SmartCardService */
SmartCardService smartCardService = SmartCardService.getInstance();
The Smart Card Service is based on the SmartCardService object, which is a singleton that must be held by the application all along its execution.
Its main role is to centralize Keyple resources and manage their lifecycle.
Choose the plugin
The Keyple application developer will choose the plugins he needs according to the equipment on which his Keyple application will run.
For example, if the environment is PC based, one will probably, but without obligation, go for the PC/SC plugin.
For an Andoid terminal environment, the plugin could be the standard Android NFC plugin or one of the plugins available from the industrial partners of the project. For a complete list of available plugins, please see the Java or C++ pages.
Register the plugin
All Keyple plugins implement the Plugin
interface.
The plugin registration consists in submitting its factory to the Smart Card Service.
/* Assign the PcscPlugin to the SmartCardService */
plugin = smartCardService.registerPlugin(new PcscPluginFactory(null, readerExceptionHandlerImpl));
The plugin factories all implement the interface expected by SmartCardService.
Depending on the case, the constructor of the factory provided by the plugin can take parameters as argument.
For example, in the code above, the PC/SC plugin expects exception handlers, but in other cases it could be other parameters.
Observation of the plugin
The observation of reader connections and disconnections is achieved through a background task managed by Keyple Core.
It is therefore imperative to provide an exception handler to allow Keyple Core to warn the application in case of an execution error during monitoring or event notification.
Here is an example of exception handler implementation in a PC/SC plugin context:
...
private static class PluginExceptionHandlerImpl implements PluginObservationExceptionHandler {
@Override
public void onPluginObservationError(String pluginName, Throwable throwable) {
logger.error("An unexpected plugin error occurred: {}", pluginName, throwable);
}
}
}
/* Create an exception handler for plugin observation */
PluginExceptionHandlerImpl pluginExceptionHandlerImpl = new ExceptionHandlerImpl();
/* Assign the PcscPlugin to the SmartCardService */
plugin = smartCardService.registerPlugin(new PcscPluginFactory(pluginExceptionHandlerImpl, null));
...
For the observation of the plugin itself, the application must provide
an object implementing the PluginObserver
interface to the plugin
after having cast it in ObservablePlugin
.
((ObservablePlugin) plugin).addObserver(new PluginObserver());
The PluginObserver
interface requires the implementation of the
update
method that will be called by Keyple Core when notifying
plugin events.
class PluginObserver implements ObservablePlugin.PluginObserver {
@Override
public void update(PluginEvent event) {
switch (event.getEventType()) {
case READER_CONNECTED:
// here the processing to be done when a reader is connected
...
break;
case READER_DISCONNECTED:
// here the processing to be done when a reader is disconnected
...
break;
default:
break;
}
}
}
}
Retrieve the reader
Readers are objects implementing the Reader
interface and are
returned by the plugin’s getReader
method taking the name of the
reader as argument.
The names of the readers available from the plugin are returned as a
list of strings by the getReaderNames
method.
The getReaders
method also allows to retrieve all readers in a Map
whose key is the name of the reader and the value the Reader
object.
Here is an example to get the 1st PC/SC reader:
String readerName = plugin.getReaderNames().get(0);
Reader reader = plugin.getReader(readerName);
Customize the reader settings
Take a close look at the parameters proposed by the plugin and its readers.
In particular, it is necessary to configure the expected communication protocols, but it is also possible that other settings exist depending on the hardware context.
Observation of the reader
The observation of inserting and removing cards from readers is similar to the observation of plugins in that it requires the same operations, i.e. the use of an exception handler and an object implementing a dedicated interface.
...
private static class ReaderExceptionHandlerImpl implements ReaderObservationExceptionHandler {
@Override
public void onReaderObservationError(String pluginName, String readerName, Throwable throwable) {
logger.error("An unexpected reader error occurred: {}:{}", pluginName, readerName, throwable);
}
}
}
/* Create an exception handler for reader observation */
ReaderExceptionHandlerImpl readerExceptionHandlerImpl = new ExceptionHandlerImpl();
/* Assign the PcscPlugin to the SmartCardService */
plugin = smartCardService.registerPlugin(new PcscPluginFactory(pluginExceptionHandlerImpl, readerExceptionHandlerImpl));
...
The observation of the events of the reader is done in a similar way to that of the plugin, by adding an observer:
((ObservableReader) reader).addObserver(new ReaderObserver());
and implementing the ReaderObserver interface:
class ReaderObserver implements ObservableReader.ReaderObserver {
@Override
public void update(ReaderEvent event) {
switch (event.getEventType()) {
case CARD_INSERTED:
// here the processing to be done when a card is inserted
...
break;
case CARD_MATCHED:
// here the processing to be done when a card matched the selection
...
break;
case CARD_REMOVED:
// here the processing to be done when a card is removed
...
break;
default:
break;
}
}
}
}
Reader
interface).Card selection
The card selection service offered by Keyple Core gives multiple possibilities to choose the processing according to the type of card presented to the reader.
It is based on a filtering process according to three possible criteria, each of which is optional:
- the communication protocol of the card (usually also identifying a card technology)
- the answer to reset of the card (ATR)
- the ISO standardized application identifier (AID)
Each of these criteria can be defined in a CardSelector
object.
When a card is inserted, it is evaluated according to these criteria and will be given the status “selected” or not.
When a card is not selected, no other operation will be possible with it. Depending on the chosen setting, the result of the selection will or will not be made available to the application. It is thus possible to directly ignore cards that do not correspond to the defined selection criteria.
When a card is selected, the result is an object that extends the AbstractSmartCard and contains all the information known about the card at that stage.
In the case of a ISO standardized card, the application is selected with the provided AID (additional settings are available to specify the desired navigation within the card applications list).
In addition to the selection process itself, specific APDU commands can
be sent to the card if the selection is successful. The output data of
these commands are available in the instance of the object
AbstractSmarCard
.
The CardSelector
and the additional APDU commands are grouped in a
CardSelectionRequest
object.
One or more CardSelectionRequest
can be set up to perform as many
selection cases, each targeting a particular card or application.
The final selection process takes as input a list of
CardSelectionRequest
and gets in return a list of
CardSelectionResponse
.
Card selection steps
In this guide we will not show the addition of supplementary APDU commands. Please refer to the Calypso guide for an implementation example.
Create the card selection service
The card selection service will be used all along the card search process.
cardSelectionService = new CardSelectionsService();
Create the selection cases
The application can create as many selection cases as the type of cards expected. The order in which the selection cases are prepared is important because it will favor the latency delay for the processing of the cards corresponding to the first case. It is therefore recommended to place the most common card profile in the application context first.
/** Create a new class extending AbstractCardSelection */
public final class GenericCardSelection extends AbstractCardSelection {
public GenericCardSelection(CardSelector cardSelector) {
super(cardSelector);
}
@Override
protected AbstractSmartCard parse(CardSelectionResponse cardSelectionResponse) {
class GenericSmartCard extends AbstractSmartCard {
public GenericSmartCard(CardSelectionResponse cardSelectionResponse) {
super(cardSelectionResponse);
}
public String toJson() {
return "{}";
}
}
return new GenericSmartCard(cardSelectionResponse);
}
}
final String aid1 = "AABBCCDDEE";
final String aid2 = "EEDDCCBBAA";
// first selection case targeting cards with AID1
GenericCardSelection cardSelector1 =
new GenericCardSelection(
CardSelector.builder()
.cardProtocol(ContactlessCardCommonProtocols.ISO_14443_4.name())
.aidSelector(CardSelector.AidSelector.builder().aidToSelect(aid1).build())
.build());
// Add the selection case to the current selection
cardSelectionsService.prepareSelection(cardSelector1);
// first selection case targeting cards with AID1
GenericCardSelection cardSelector2 =
new GenericCardSelection(
CardSelector.builder()
.cardProtocol(ContactlessCardCommonProtocols.ISO_14443_4.name())
.aidSelector(CardSelector.AidSelector.builder().aidToSelect(aid2).build())
.build());
// Add the selection case to the current selection
cardSelectionsService.prepareSelection(cardSelector2);
Proceed to the selection with a non-observable reader
The processExplicitSelections
method of CardSelectionService
performs the actual communication with the card.
...
// Check if a card is present in the reader
if (!reader.isCardPresent()) {
logger.error("No Po Card is present in the reader.");
return;
}
// Actual card communication: operate through a single request the card selection
CardSelectionsResult cardSelectionsResult =
cardSelectionsService.processExplicitSelections(reader);
...
Proceed to the selection with an observable reader
In the case of an observable reader, the selection request is provided
to the reader (it is then named Default Selection) and will be processed
automatically as soon as a card is presented. The application is then
notified of the event with the data resulting from the selection.
Depending on the selection settings, the application will be notified of
all card presentations (CARD_INSERTED
event) or only those
presentations that led to a successful selection (CARD_MATCHED
event).
Add a default selection
// Provide the Reader with the selection operation to be processed when a card is inserted.
((ObservableReader) reader)
.setDefaultSelectionRequest(
cardSelectionService.getDefaultSelection().getDefaultSelectionsRequest(),
ObservableReader.NotificationMode.MATCHED_ONLY,
ObservableReader.PollingMode.REPEATING);
The NotificationMode
allows you to specify whether all card
insertions should be reported to the application or only those that led
to a successful selection.
PollingMode
indicates whether to go back to waiting for the card
after processing (REPEATING
) or let the application decide when to
restart the search (SINGLESHOT
) with startCardDetection
.
Note: when the default selection is set with the PollingMode
parameter, the card detection is started automatically. However, it is
possible to set a default selection without automatic start and by
starting the detection independently with startCardDetection
.
Receive the result as an event
...
@Override
public void update(ReaderEvent event) {
switch (event.getEventType()) {
case CARD_MATCHED:
AbstractSmartCard selectedCard = null;
try {
selectedCard =
getDefaultSelection()
.processDefaultSelectionsResponse(event.getDefaultSelectionsResponse())
.getActiveSmartCard();
} catch (KeypleException e) {
logger.error("Exception: {}", e.getMessage());
((ObservableReader) (event.getReader())).finalizeCardProcessing();
}
if (selectedCard != null) {
logger.info("Observer notification: the selection of the card has succeeded.");
// insert the processing of the card here
...
logger.info("= #### End of the card processing.");
} else {
logger.error(
"The selection of the card has failed. Should not have occurred due to the MATCHED_ONLY selection mode.");
}
break;
case CARD_INSERTED:
logger.error(
"CARD_INSERTED event: should not have occurred due to the MATCHED_ONLY selection mode.");
break;
case CARD_REMOVED:
logger.trace("There is no PO inserted anymore. Return to the waiting state...");
break;
default:
break;
}
if (event.getEventType() == ReaderEvent.EventType.CARD_INSERTED
|| event.getEventType() == ReaderEvent.EventType.CARD_MATCHED) {
// Informs the underlying layer of the end of the card processing, in order to manage the
// removal sequence.
((ObservableReader) (event.getReader())).finalizeCardProcessing();
}
}
...
Get the selection result
The result of the selection is available in the AbstractSmartCard
object.
...
if (!cardSelectionsResult.hasActiveSelection()) {
logger.warn("The selection of the application " + cardAid + " failed.");
}
AbstractSmartCard smartCard = cardSelectionsResult.getActiveSmartCard();
logger.info("The selection of the card has succeeded.");
if (smartCard.hasFci()) {
String fci = ByteArrayUtil.toHex(smartCard.getFciBytes());
logger.info("Application FCI = {}", fci);
}
if (smartCard.hasAtr()) {
String atr = ByteArrayUtil.toHex(smartCard.getAtrBytes());
logger.info("Card ATR = {}", atr);
}
...
Implementation of the application service
The applicative processing of the card that follows the selection of the
card is to be inserted in the processing of the CARD_INSERTED
or
CARD_MATCHED
event.
It can be processed in the thread provided by the monitoring task or detached in a separate thread. The application developer must pay attention to the handling of exceptions in this part of the application. Indeed, in case of a runtime exception, the information will be given to the application via the exception handler configured beforehand.
Stopping the application
The clean shutdown of a Keyple application requires the release of resources and in particular the shutdown of the observation threads.
This is done by unregistering the plugins in the following way:
smartCardService.unregisterPlugin(plugin.getName());
Keyple Core API
To learn all the details of the Keyple Core API, please consult the Javadoc documentation.
However, here are two diagrams showing the main features of Keyple Core:
The diagram below represents the main classes implemented around the Smart Card Service with in particular the observation mechanisms.
The diagram below represents the main classes used for selection operations.
Examples
To help in the implementation of the different facilities offered by Keyple to process smart cards, a set of examples is present in the project repository examples
Nevertheless, you will find below a brief description of them:
Explicit Selection
Shows the use of Keyple to make a card selection without observing the reader, based on testing the presence of the card by the application.
Default Selection
Shows the use of Keyple to make a card selection with observation of the reader. A default selection is prepared, the presentation of a card triggers the notification of a reader event to the application.
Sequential Multiple Selection
Executes successively several independent selection operations with the use of the ISO ‘NEXT’ navigation flag.
Illustrates the case of a card exploration with maintenance of the physical channel open.
Grouped Multiple Selection
Executes a multiple selection with logical channel closure between each selection.
Allows the exploration of the applications of a card in a single operation but without selection at the end.
Demo Card Protocol Detection
Demonstrates the use of Keyple in a context where several card technologies are likely to be processed by the application.
Demo Observable Reader Notification
Demonstrates the use of Keyple to implement the observation of a plugin and its readers. Readers are dynamically created and an observer is assigned to them.
Download
The artifact Keyple Core and how to integrate it into your application is available here: