();
    Integer channelIdCounter = 0;
    Omapi omapi = this;
    /**
     * Create a new OMAPI client instance.
     * @param context application environment context
     * @param callback instance of OmapiCallback that implements the OMAPI callback methods
     */
    Omapi(Context context, OmapiCallback callback) {
        seService = new SEService(context, new syncExec(), mListener);
        assert(callback != null);
        this.callback = callback;
        Log.d("OMAPI", "Connecting SE-Service...\n");
    }
    private final OnConnectedListener mListener = new OnConnectedListener() {
        @Override
        public void onConnected() {
            Log.d("OMAPI", "SE-Service connection successful!\n");
            callback.omapiConnected(omapi);
        }
    };
    private class syncExec implements Executor {
        public void execute(Runnable r) {
            r.run();
        }
    }
    //Ensure SE-Service is present and connected
    private void ensureSeService() throws Exception {
        if (seService == null)
            throw new Exception("cannot get channel, SE-Service not present!");
        if (!seService.isConnected())
            throw new Exception("cannot get channel, SE-Service not connected!");
    }
    //Get a session for a specified reader. In case the specified reader has no session open yet,
    //create a new one. Sessions stay open throughout the whole lifetime of this object.
    private Session getOrCreateSession(String readerName) throws Exception {
        Session session;
        Reader[] readers = seService.getReaders();
        for (Reader reader : readers) {
            if (reader.getName().equals(readerName) && reader.isSecureElementPresent()) {
                if (sessions.containsKey(readerName)) {
                    session = sessions.get(readerName);
                    assert (session != null);
                } else {
                    session = reader.openSession();
                    sessions.put(readerName, session);
                }
                return session;
            }
        }
        throw new Exception("reader " + readerName + " not found!\n");
    }
    /**
     * Scan for useable readers.
     * The result is a Map that contains the available readers as key value pairs, where the
     * key is the reader name and the value the ATR of the card. Empty readers will be ignored.
     * @return Map that contains reader names and card ATR values
     */
    public Map scan() {
        try {
            Map usable_readers = new HashMap();
            ensureSeService();
            Reader[] readers = seService.getReaders();
            if (readers.length == 0)
                throw new Exception("no reader available!");
            for (Reader reader : readers) {
                if (reader.isSecureElementPresent()) {
                    Session session = reader.openSession();
                    byte[] atr = session.getATR();
                    if (atr != null) {
                        Log.d("OMAPI", "found reader: " + reader.getName() + " (ATR=" + Utils.b2h(atr) + ")\n");
                        usable_readers.put(reader.getName(), atr);
                    } else {
                        Log.d("OMAPI", "found reader: " + reader.getName() + " (no ATR, unresponsive?)\n");
                    }
                    session.close();
                } else {
                    Log.d("OMAPI","found reader: " + reader.getName() + " (empty)\n");
                }
            }
            return usable_readers;
        } catch (Exception e) {
            Log.e("OMAPI",e.getMessage() + "\n");
            return null;
        }
    }
    /**
     * Perform an APDU transaction on the card.
     * @param readerName string that contains the reader name (e.g. "SIM1")
     * @return array of bytes that contains the ATR (from card) or, throws Exception on error
     */
    public byte[] getAtr(String readerName) throws Exception {
        try {
            ensureSeService();
            Session session = getOrCreateSession(readerName);
            byte[] atr = session.getATR();
            if (atr != null) {
                Log.d("OMAPI", "ATR: " + Utils.b2h(atr) + "\n");
                return atr;
            }
            throw new Exception( "ATR request failed!\n");
        } catch (Exception e) {
            Log.e("OMAPI", e.getMessage() + "\n");
            throw e;
        }
    }
    /**
     * Open a channel on the specified reader to the specified AID.
     * @param readerName string that contains the reader name (e.g. "SIM1")
     * @param aid array of bytes that contains the AID of the application to access
     * @return OMAPI Channel number on success, throws Exception on error
     */
    public int open(String readerName, byte[] aid) throws Exception {
        try {
            ensureSeService();
            Session session = getOrCreateSession(readerName);
            Channel channel = session.openLogicalChannel(aid);
            if (channel == null)
                throw new Exception(String.format("could not open channel for AID (%s) on reader: %s!\n",
                        Utils.b2h(aid), readerName));
            Log.d("OMAPI", String.format("sucessfully opend channel for AID (%s) on reader: %s\n",
                    Utils.b2h(aid), readerName));
            channelIdCounter++;
            channels.put(channelIdCounter, channel);
            return channelIdCounter;
        } catch (Exception e) {
            Log.e("OMAPI",e.getMessage() + "\n");
            throw e;
        }
    }
    /**
     * Perform an APDU transaction on the card.
     * @param channelId id-number of the OMAPI Channel
     * @param apdu array of bytes that contains the command APDU (to card)
     * @return array of bytes that contains the response APDU (from card) or throws Exception on error
     */
    public byte[] transact(int channelId, byte[] apdu) throws Exception {
        try {
            ensureSeService();
            if (!channels.containsKey(channelId))
                throw new Exception(String.format("no channel open under channelId = %d", channelId));
            Channel channel = channels.get(channelId);
            Log.d("OMAPI","APDU TX: " + Utils.b2h(apdu) + "\n");
            byte[] response = channel.transmit(apdu);
            if (response == null)
                throw new Exception("unresponsive card!");
            Log.d("OMAPI","APDU RX: " + Utils.b2h(response) + "\n");
            return response;
        } catch (Exception e) {
            Log.e("OMAPI",e.getMessage() + "\n");
            throw e;
        }
    }
    /**
     * Close an OMAPI channel.
     * @param channelId id-number of the OMAPI Channel
     */
    public void close(int channelId) {
        try {
            ensureSeService();
            if (!channels.containsKey(channelId))
                throw new Exception(String.format("no channel open under channelId = %d", channelId));
            Channel channel = channels.get(channelId);
            Log.d("OMAPI", String.format("closing reader channel %d ...\n", channelId));
            channel.close();
            Log.d("OMAPI", String.format("channel %d closed.\n", channelId));
            channels.remove(channel);
        } catch (Exception e) {
            Log.e("OMAPI",e.getMessage() + "\n");
        }
    }
    /**
     * Shutdown OMAPI SE-Service. This shuts down the SE-Service. There is no method to recover
     * from the shutdown state again. The APU user must create a new instance.
     */
    public void shutdown() {
        /* Inform callback handler that the SE-Service is going to disconnect. */
        callback.omapiDisconnected();
        /* Make sure all channels and sessions are orderly closed. */
        for (Channel channel : channels.values())
            channel.close();
        for (Session session : sessions.values())
            session.close();
        sessions = new HashMap();
        channels = new HashMap();
        /* Shutdown SE-Service */
        seService.shutdown();
    }
}