How to communicate with HostApduService from an Ac

2019-04-10 12:45发布

问题:

I have asked this question here but it was marked as duplicate - however I didn't find any solution helpful mentioned in comments. Here, I am asking again with more details ...

I am doing a sample app (PoC) on HCE and using HostApduService as per Android user guide. I have created two apps
1) ReaderApp - acting as card reader 2) HCEApp - emulating a card

In HCEApp, I have created a class 'MyService' extending HostApduService

public class MyService extends HostApduService {

private int messageCounter;
private final String TAG = "MyService";

Intent mIntent;

@Override
public void onCreate() {
    super.onCreate();
    Log.i(TAG, "onCreate");

    mIntent = new Intent(this, MyActivity.class);
    mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(mIntent);
}

/**
 * returned bytes will be sent as response. This method runs in Main thread
 * so return ASAP.
 */

@Override
public byte[] processCommandApdu(byte[] apdu, Bundle extras) {
    if (selectAidApdu(apdu)) {
        Log.i(TAG, "Application selected");
        return getWelcomeMessage();
    } else {
        Log.i(TAG, "Received: " + new String(apdu));
        return getNextMessage();
    }
}

private byte[] getWelcomeMessage() {
    return "Hello Desktop!".getBytes();
}

private byte[] getNextMessage() {
    return ("Message from android: " + messageCounter++).getBytes();
}

private boolean selectAidApdu(byte[] apdu) {

    if (apdu != null) {
        for (byte b : apdu) {
            System.out.printf("0x%02X", b);
        }
    }

    return apdu.length >= 2 && apdu[0] == (byte) 0
            && apdu[1] == (byte) 0xa4;
}

@Override
public void onDeactivated(int reason) {
    Log.i(TAG, "Deactivated: " + reason);
}

@Override
public boolean onUnbind(Intent intent) {
    return super.onUnbind(intent);
}

}

As you can see in onCreate(), I am launching MyActivity provides user to enter some information and needs to be sent back to MyService.

I think I can not use binding as 'onBind()' is declared final in HostApduService as below

@Override
public final IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
}

Please let me know if I am understading it correctly. Appreciate any help.

Thanks
iuq

回答1:

Whether you can use onBind or not I do not know, but I recently worked with a BroadcastReceiver from which I had to start a Service. You cannot bind a Service from a BroadcastReceiver according to docs, you can only start it. I needed to send some data to the Service from my BroadcastReceiver at some later point, and since the binder techniques was not available to me, I had to find a different way to communicate with the Service, much like your case where you don't have a reference to it.

I did some research but could not find any solution, but then I remembered that you can pass intent data with the startService(intent) call. I start my Service work in onCreate instead, as onCreate is only called once when the Service is created.

In your Activity

public void sendDataToService(){
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra("message", SOME_DATA);
    context.startService(intent);
}

In your Service

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    // Check if intent has extras
    if(intent.getExtras() != null){

        // Get message
        int message = intent.getExtras().getInt("message");
    }

    return START_NOT_STICKY;
}

This may be some sort what of a hack since "startService" does not sound like it should be used to send messages, and am not sure if this is exactly what you need, but it worked for me, so I hope it works for you. Cheers

Edit: BTW. I use it to tell a LocationService that a particular activity no longer want location updates.



回答2:

I ended up taking a different approach to solving this same problem. When I bind to my HostApduService subclass, I grab a handle to the Messenger interface returned by the HostApduService onBind implementation.

Here's some sample code. This would all go in your activity implementation (calling it MyActivity here, communicating with MyHostApduServiceSubclass). Here's what MyActivity would need to include:

private Messenger mAPDUMessenger;
...
@Override
protected void onStart() {
    super.onStart();
    Context context = getApplicationContext();
    Intent apduIntent = new Intent(montext, ContactlessApduService.class);
    context.bindService(apduIntent, mAPDUConnection, Context.BIND_AUTO_CREATE);
}
...
private ServiceConnection mAPDUConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        // The HostApduService has a final override on the onBind() service method that returns
        // an IMessageHandler interface that we can grab and use to send messages back to the
        // terminal - would be better to get a handle to the running instance of the service so
        // that we could make use of the HostApduService#sendResponseApdu public method
        mAPDUMessenger = new Messenger(service);
        registerAPDUMessengerIntentFilters();
        // ^ This method sets up my handlers for local broadcast messages my BroadcastReceiver processes.
    }
...
}
...
private void registerAPDUMessengerIntentFilters() {
    LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(MyActivity.this);

    IntentFilter intentFilter = new IntentFilter(MyHostApduServiceSubclass.ACTION_PPSE_APDU_SELECT);
    lbm.registerReceiver(apduMessageBroadcastReceiver, intentFilter);
}
...
BroadcastReceiver apduMessageBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(MyHostApduServiceSubclass.ACTION_PPSE_APDU_SELECT)) {
            sendResponseApdu(MyActivity.PPSE_APDU_SELECT_RESPONSE_BYTES);
        }
    }
};
...
public final void sendResponseApdu(byte[] responseApdu) {
    Message responseMsg = Message.obtain(null, MyHostApduServiceSubclass.MSG_RESPONSE_APDU);
    // ^ Note here that because MSG_RESPONSE_APDU is the message type
    //   defined in the abstract HostApduService class, I had to override
    //   the definition in my subclass to expose it for use from MyActivity.
    //   Same with the KEY_DATA constant value below.
    Bundle dataBundle = new Bundle();
    dataBundle.putByteArray(MyHostApduServiceSubclass.KEY_DATA, responseApdu);
    responseMsg.setData(dataBundle);
    try {
        mAPDUMessenger.send(responseMsg);
    } catch (RemoteException e) {
        // Do something with the failed message
    }
}

And then your HostApduService subclass would just need to send a broadcast to your activity indicating what APDU command was received. Here is what would need to be included in MyHostApduServiceSubclass:

public static final String ACTION_PPSE_APDU_SELECT = "ACTION_PPSE_APDU_SELECT";

// Abstract super class constant overrides
public static final String KEY_DATA = "data";
public static final int MSG_RESPONSE_APDU = 1;

@Override
public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
    Context context = getApplicationContext();
    LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
    if (Arrays.equals(MyHostApduServiceSubclass.PPSE_APDU_SELECT_BYTES, commandApdu)) {
        lbm.sendBroadcast(new Intent(ACTION_PPSE_APDU_SELECT));
    }
    return null;
    // ^ Note the need to return null so that the other end waits for the
    //   activity to send the response via the Messenger handle
}