I am trying to create an application that transmits APDUs to a SIMCARD using the OMAPI libraries, but the OnConnected listener for SEService is never called.
I followed the structure I found here: https://android.googlesource.com/platform/cts/+/master/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java
I wait up to 30 seconds to see if the OnConnectedListener
method is called, but that never happens. I always get the error that I left in the code: throw new TimeoutException( "Service could not be connected after " + SERVICE_CONNECTION_TIME_OUT + "ms");
The SIMCARD I use contains the ARA-M applet, and the necessary SHA-1 keys, that is, the application obtains carrier privileges. In another application using the TelephonyManager libraries I have had success transmitting APDUs.
Please can you help me understand if I am doing something wrong? Do I need to declare some permission in the manifest of my application?
Thank you in advance.
I leave you part of the code that I implemented:
public class MainActivity extends AppCompatActivity [...]{
/*FOR OMAPI*/
private final Object serviceMutex = new Object();
private SEService seService;
private boolean connected = false;
private Timer connectionTimer;
private ServiceConnectionTimerTask mTimerTask = new ServiceConnectionTimerTask();
private final long SERVICE_CONNECTION_TIME_OUT = 30000;
//////////////////////////////////////////////////////////////////////////////
@Override
public void onCreate(Bundle savedInstanceState) {
[...]
setUp(getApplicationContext());
try {
waitForConnection();
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
Reader[] readers = seService.getReaders();
byte [] apdu = {(byte)0x80, (byte)0x02, (byte)0xfC, (byte)0x00};
for (Reader reader : readers){
byte [] response = new byte[0];
try {
response = Util.internalTransmitAPDU(reader, apdu);
} catch (IOException e) {
throw new RuntimeException(e);
}
Log.i("response OMAPI", String.valueOf(response));
}
}
public void setUp(Context context){
seService = new SEService(context, new SynchronousExecutor(), mListener);
connectionTimer = new Timer();
connectionTimer.schedule(mTimerTask, SERVICE_CONNECTION_TIME_OUT);
}
static class SynchronousExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
private final SEService.OnConnectedListener mListener = () -> {
synchronized (serviceMutex){
connected = true;
serviceMutex.notify();
Log.i("OMAPI", "EntraOnconnect");
}
private void waitForConnection() throws TimeoutException {
synchronized (serviceMutex) {
if (!connected) {
try {
serviceMutex.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!connected && !seService.isConnected()) {
throw new TimeoutException(
"Service could not be connected after " + SERVICE_CONNECTION_TIME_OUT
+ " ms");
}
if (connectionTimer != null) {
connectionTimer.cancel();
}
}
}
}
@k_o_, Apologies for responding late.
I returned to the project and thanks to your comment "Maybe your code is blocked if not running in a different thread" I found that it was definitely a thread-related problem:
Sorry if I don't mention any term appropriately, I'm not an android expert,
From what I understood, OMAPI should work asynchronously. In my case, I was implementing everything related to OMAPI Synchronously. I had a button that when pressed tries to transmit an APDU, but the app stopped, perhaps because it tried to run everything in the main thread, when the ideal is for it to be in separate threads.
I was able to get it working by implementing a ViewModel type structure:
public class OMAPIViewModel extends ViewModel {
static byte[] transmitResponse = null;
static Session session = null;
static Channel channel = null;
private static SEService seService;
static Executor executor = Executors.newSingleThreadExecutor();
private static byte []bAID;
private static byte []bAPDU;
private static final MutableLiveData<byte[]> responseAPDU = new MutableLiveData<>();
private static final MutableLiveData<Boolean> transmitError = new MutableLiveData<>();
private static final SEService.OnConnectedListener mListenerOMAPI = () -> {
Reader[] readers = new Reader[0]; // Getting all readers in this device
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
readers = seService.getReaders();
for (Reader reader : readers) {
if (Objects.equals(reader.getName(), "SIM1")) { // Checking if SE/SIM is present in the reader
Log.i("TransmitAPDUOmapi", "Transmitting APDU through the reader " + reader.getName());
try {
session = reader.openSession();
channel = session.openLogicalChannel(bAID, (byte) 0x00);
if (channel != null) {
transmitResponse = channel.transmit(bAPDU);
responseAPDU.postValue(transmitResponse);
}
} catch (SecurityException | IOException e) {
e.printStackTrace();
transmitError.postValue(true);
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
if (seService != null && seService.isConnected())
seService.shutdown();
}
}
}
}
};
public LiveData<byte[]> getResponseAPDU_OMAPI() {
return responseAPDU;
}
public LiveData<Boolean> getErrorTransmit(){return transmitError;}
/**
* This method need to be used with OMAPIViewModel.getResponseAPDU_OMAPI() to get a MutableLiveData object responseAPDU,
* You need to add an observer to get the APDU Response
* Example:
* omapiViewModel.getResponseAPDU_OMAPI().observe(this,apdu -> {
* Toast.makeText(getApplicationContext(),Arrays.toString(apdu),Toast.LENGTH_LONG).show();
* });
*
* **/
public void TransmitAPDU_OMAPI(Context context, byte []AID, byte [] apdu){
bAID = AID;
bAPDU = apdu;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
seService = new SEService(context, executor, mListenerOMAPI);
}
}
}
In order to use the TransmitAPDU_OMAPI(Context context, byte []AID, byte [] apdu) method, you must first establish an Observer that notifies the response that the transmission had, in my case I did it this way:
case R.id.buttonOMAPI:
if(!omapiViewModel.getResponseAPDU_OMAPI().hasActiveObservers())
{
omapiViewModel.getResponseAPDU_OMAPI().observe(this,apdu ->
Toast.makeText(getApplicationContext(), "Response: "+BytesBits.byteArrayToHexString(apdu).toUpperCase(),Toast.LENGTH_LONG).show()
omapiViewModel.getErrorTransmit().observe(this, errorOcurred ->
Toast.makeText(getApplicationContext(), "Security or I/O Exception ",Toast.LENGTH_LONG).show());
}
omapiViewModel.TransmitAPDU_OMAPI(this,AID_Test,test_APDU);
break;
I hope it is useful to the community. Please, if you have a suggestion to improve it, I will be attentive. Thank you so much.