Skip to content

Card reader integration

The Card Reader Interface is a mechanism that runs in the background and allows the reading of a Portuguese Citizen Card Data – i.e., personal data and address. The interface also allows generic card data reading by running a list of APDU commands. 

This section aims to support integrators in providing the necessary elements for configuring their applications to use the Card Reader Interface Service and is divided into three sections: 

Step 1: Declaring the AIDL file with the service methods
Step 2: Connect to the service
Step 3: Defining the methods for card reading

Step 1:  Declaring the AIDL file with the service methods 

To use this interface, the app that invokes it must declare an AIDL file with the methods that the service provides. This file is identical both in the service and in the app and must be in the same package in both cases.  

// ICardReaderInterface.aidl
package genericcardservice.service;

// Declare any non-default types here with import statements

interface ICardReaderInterface {
    /**
    * Demonstrates some basic types that you can use as parameters
    * and run values in AIDL
    */
    String getCitizenCardData(boolean withAddress, String pin);
    String getGenericCardData(int slot, in String[] apduCommands);
    void loadAIDS(String content);
}

The methods will be available after the first build. 

Step 2: Connect to the service

When starting the application/feature that needs to use the service, the first thing to do is to create a BrodcastReceiver to receive the intents coming from the service. For that purpose, you can create an extended class of BroadcastReceiver, as illustrated below: 

public class CartaoCidadaoReceiver extends BroadcastReceiver {
    private static final String TAG = "MyBroadcastReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.w(TAG,"Action received!");
        String result = intent.getStringExtra("data");
        if (result != null) {
            Log.w(TAG, result);
        }
    Toast.makeText(context, result, Toast.LENGTH_LONG).show();
    }
}

Subsequently, create an object of this type, which must be registered as receiver. The past actions are used if you want to receive the response data from the card via broadcast receiver. 

After registering the receiver, you can bind to the service. For that purpose, you can use the code shown below:

private void initConnection() {
    Log.w("Binding", "INIT CARD SERVICE");
    if(cardService == null) {
        Intent intent = new Intent(ICardReaderInterface.class.getName());

        /* this is service name which has been declared in the server's manifest file in service's intent-filter */
        intent.setAction("service.cardreader");

        /* From 5.0 annonymous intent calls are suspended so replacing with server app's package name */
        intent.setPackage("com.example.cartaocidadaoservice");

        //binding to remove service
        bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);
    }
}

The Package defined is that of the app that launches the service.

The serviceConnection variable used in bindService can be declared as shown below:

private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        Log.d(Tag, "Service Connected");
        cardService = ICardReaderInterface.Stub.asInterface((IBinder) iBinder);
    }
    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        Log.d(Tag, "Service Disconnected");
        cardService = null;
    }
}

To check if the service is running, the method shown below can be used. In this method, the name of the intended service is passed and, as a result, a Boolean is returned informing the service status.  

Example:

isMyServiceRunning ("genericcardservice.service.CardReaderService")
private boolean isMyServiceRunning(String serviceClass) {
    ActivityManager manager = (ActivityManager)
    getSystemService(Context.ACTIVITY_SERVICE);
    for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)){
        if(serviceClass.equalsIgnoreCase(service.service.getClassName())) {
            return true;
        }
    }
    return false;
}

Upon ending the service, if you leave the context where it was used (destroy the activity/fragment, for example), you shall do the service unbinding and unregister the receiver. 

@Override
protected void onDestroy() {
    super.onDestroy();
    unbindServices(serviceConnection);
    unregisterReceiver(br);
}

In response to this method, you get a String which includes a JSON with the different fields read: Citizen Card and Other Cards. 

Step 3: Defining the methods for card reading

Citizen card

After a successful binding to the service, the methods provided can be invoked.

To read the Citizen Card, the method is getCitizenCardData and it receives two input parameters:

  • The first parameter is a Boolean that indicates whether the address data is to be obtained in addition to the personal data. If this is the case, the flag should change to true.
  • The second parameter is the PIN of the address, which, if not changed, will be “0000”. It should be noted that this PIN is not the same as the PIN for the digital mobile key. If the PIN is incorrect, the maximum number of attempts is three. 
try {
    String result = cardService.getCitizenCardData(true, "0000");
    Log.w(Tag, result != null ? result : "-----");
}

catch (RemoteException e) {
    Log.w(Tag, Objects.requireNonNull(e.getMessage()));
    e.printStackTrace();
}
Other cards
try {
    String[] apdus = new String[]{
        "942080000430303030",
        "94A408000022000",
        "00A40008315449432E494341",
        "00C0000024",
        "94A4090000",
        "00C0000019",
        "942000000430303030",
        "94B2011C18",
        "948A8A38045B9DED73",
        "00C0000000",
        "00C0000022",
        "94B201441D",
        "94B202441D",
        "94B203441D",
        "94B201CC1D",
        "94B2014C1D",
        "94B2024C1D",
        "94B2934C1D",
        "94B2044C1D",
        "94B201EC1D",
        "94B201F41D",
        "948E00000428870088",
        "00C0000004",
    }
    String result = cardService.getGenericCardData(6, apdus);
    Log.w(Tag, result != null ? result : "-----");
} catch (RemoteException e) {
    Log.w(Tag, Objects.requireNonNull(e.getMessage()));
    e.printStackTrace();
}

The available types of slots are listed in the Enum illustrated below:

public enum CardSlotTypeEnumICC1ICC2ICC3PSAM1PSAM2PSAM3RFSWIPEprivate CardSlotTypeEnum () {
    }
}

As for the previous method described the response to this method is a String which includes a JSON with the information obtained.