package com.handstandsam.handstandpay.apdu; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.nfc.cardemulation.HostApduService; import android.os.Build; import android.os.Bundle; import android.os.Vibrator; import android.support.v4.content.LocalBroadcastManager; import com.handstandsam.handstandpay.activity.PayActivity; import com.handstandsam.handstandpay.util.HexUtil; import com.handstandsam.handstandpay.util.MagStripeParser; import com.handstandsam.handstandpay.util.PreferencesUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.regex.Matcher; /** * Terminology * APDU - Proximity Payment System Environment * PPSE - Proximity Payment System Environment */ @TargetApi(Build.VERSION_CODES.KITKAT) public class HandstandApduService extends HostApduService { public static final String PAYMENT_SENT = "PAYMENT_SENT"; private static final Logger logger = LoggerFactory.getLogger(HandstandApduService.class); boolean isProcessing = false; @Override public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) { if (!isProcessing) { startPaymentActivityInBackgroundThread(); isProcessing = true; } String inboundApduDescription; byte[] responseApdu; if (Arrays.equals(ApduCommands.PPSE_APDU_SELECT, commandApdu)) { inboundApduDescription = "Received PPSE select: "; responseApdu = ApduCommands.PPSE_APDU_SELECT_RESP; } else if (Arrays.equals(ApduCommands.VISA_MSD_SELECT, commandApdu)) { inboundApduDescription = "Received Visa-MSD select: "; responseApdu = ApduCommands.VISA_MSD_SELECT_RESPONSE; } else if (ApduCommands.isGpoCommand(commandApdu)) { inboundApduDescription = "Received GPO (get processing options): "; responseApdu = ApduCommands.GPO_COMMAND_RESPONSE; } else if (Arrays.equals(ApduCommands.READ_REC_COMMAND, commandApdu)) { inboundApduDescription = "Received READ REC: "; String rawSwipeData = PreferencesUtil.getRawSwipeData(getApplicationContext()); responseApdu = getReadRecordResponse(rawSwipeData); } else { inboundApduDescription = "Received Unhandled APDU: "; responseApdu = ApduCommands.ISO7816_UNKNOWN_ERROR_RESPONSE; } String inputHex = HexUtil.byteArrayToHex(commandApdu); String outputHex = HexUtil.byteArrayToHex(responseApdu); logger.debug(inboundApduDescription); logger.debug("Input Hex\n" + inputHex); logger.debug("Output Hex\n" + outputHex); return responseApdu; } @Override public void onDeactivated(int reason) { isProcessing = false; Context context = getApplicationContext(); LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); lbm.sendBroadcast(new Intent(PAYMENT_SENT)); } private void startPaymentActivityInBackgroundThread() { new Runnable() { @Override public void run() { //Start Payment Activity Intent intent = new Intent(getApplicationContext(), PayActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); getApplicationContext().startActivity(intent); //Vibrate Vibrator v = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE); v.vibrate(500); } }.run(); } public byte[] getReadRecordResponse(String swipeData) { byte[] readRecResponse = null; Matcher matcher = MagStripeParser.TRACK_2_PATTERN.matcher(swipeData); if (matcher.matches()) { String track2EquivData = matcher.group(1); // convert the track 2 data into the required byte representation track2EquivData = track2EquivData.replace('=', 'D'); if (track2EquivData.length() % 2 != 0) { // add an 'F' to make the hex string a whole number of bytes wide track2EquivData += "F"; } // Each binary byte is represented by 2 4-bit hex characters int track2EquivByteLen = track2EquivData.length() / 2; readRecResponse = new byte[6 + track2EquivByteLen]; ByteBuffer bb = ByteBuffer.wrap(readRecResponse); bb.put((byte) 0x70); // EMV Record Template tag bb.put((byte) (track2EquivByteLen + 2)); // Length with track 2 tag bb.put((byte) 0x57); // Track 2 Equivalent Data tag bb.put((byte) track2EquivByteLen); // Track 2 data length bb.put(HexUtil.hexToByteArray(track2EquivData)); // Track 2 equivalent data bb.put((byte) 0x90); // SW1 bb.put((byte) 0x00); // SW2 } else { logger.warn("ApduService processed bad swipe data"); } return readRecResponse; } }