package com.github.skjolber.nfc.service; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.util.Log; import com.acs.smartcard.Reader; import com.acs.smartcard.Reader.OnStateChangeListener; import com.acs.smartcard.ReaderException; import com.acs.smartcard.RemovedCardException; import com.acs.smartcard.TlvProperties; import com.github.skjolber.nfc.hce.DefaultNfcReaderServiceListener; import com.github.skjolber.nfc.hce.IAcr1222LBinder; import com.github.skjolber.nfc.hce.IAcr122UBinder; import com.github.skjolber.nfc.hce.IAcr1251UBinder; import com.github.skjolber.nfc.hce.IAcr1252UBinder; import com.github.skjolber.nfc.hce.IAcr1255UBinder; import com.github.skjolber.nfc.hce.IAcr1281UBinder; import com.github.skjolber.nfc.hce.IAcr1283Binder; import com.github.skjolber.nfc.NfcReader; import com.github.skjolber.nfc.NfcTag; import com.github.skjolber.nfc.command.ACR1222Commands; import com.github.skjolber.nfc.command.ACR122Commands; import com.github.skjolber.nfc.command.ACR1251Commands; import com.github.skjolber.nfc.command.ACR1252Commands; import com.github.skjolber.nfc.command.ACR1255UsbCommands; import com.github.skjolber.nfc.command.ACR1281Commands; import com.github.skjolber.nfc.command.ACR1283Commands; import com.github.skjolber.nfc.command.ACRCommands; import com.github.skjolber.nfc.command.ACRReaderTechnology; import com.github.skjolber.nfc.command.ReaderWrapper; import com.github.skjolber.nfc.command.Utils; import com.github.skjolber.nfc.skjolberg.reader.operations.NdefOperations; import org.nfctools.api.TagType; import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.Set; public abstract class AbstractBackgroundUsbService extends AbstractService { public static final String PREFERENCE_AUTO_START_ON_READER_CONNECT = "preference_auto_start_on_reader_connect"; public static final String PREFERENCE_AUTO_STOP_ON_READER_DISCONNECT = "preference_auto_stop_on_reader_disconnect"; public static final String PREFERENCE_AUTO_START_ON_RESTART = "preference_auto_start_on_restart"; public static final String PREFERENCE_AUTO_READ_UID = "preference_auto_read_uid"; private static final String TAG = AbstractBackgroundUsbService.class.getName(); private static final String DESCRIPTOR = "android.nfc.INfcTag"; private static final String CLASS = "android.nfc.INfcTag"; private static class Scanner extends Handler { private static final long USB_RESCAN_INTERVAL_STANDARD = 1000; private static final long USB_RESCAN_INTERVAL_READER_DETECTED = 10000; private WeakReference<AbstractBackgroundUsbService> activityReference; public Scanner(AbstractBackgroundUsbService activity) { this.activityReference = new WeakReference<AbstractBackgroundUsbService>(activity); } void resume() { synchronized (this) { if (!hasMessages(0)) { sendEmptyMessage(0); } } } void resumeDelayed() { synchronized (this) { if (!hasMessages(0)) { sendEmptyMessageDelayed(0, USB_RESCAN_INTERVAL_STANDARD); } } } void pause() { synchronized (this) { removeMessages(0); } } @Override public void handleMessage(Message message) { //Log.v(TAG, "Handle message"); AbstractBackgroundUsbService activity = activityReference.get(); if (activity != null) { if (activity.isDetectUSBDevice()) { if (activity.detectUSBDevices()) { Log.v(TAG, "Detected USB devices"); sendEmptyMessageDelayed(0, USB_RESCAN_INTERVAL_READER_DETECTED); } else { sendEmptyMessageDelayed(0, USB_RESCAN_INTERVAL_STANDARD); } } } } } private final BroadcastReceiver usbDevicePermissionReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_USB_PERMISSION.equals(action)) { Log.d(TAG, "Usb permission action with " + intent.getExtras().keySet()); UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) { if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { Log.d(TAG, "Open reader: " + device.getDeviceName()); synchronized (AbstractBackgroundUsbService.this) { openDevices.add(device.getDeviceId()); } new OpenTask().execute(device); } else { Log.d(TAG, "Permission denied for device " + device.getDeviceName() + " / " + device.getDeviceId() + ", resume scanning."); synchronized (AbstractBackgroundUsbService.this) { refusedPermissionDevices.add(device.getDeviceId()); } readerScanner.resume(); } } else { Log.d(TAG, "Did not find any device"); } } } }; private final BroadcastReceiver usbDeviceDetachedReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { Log.d(TAG, "Usb device detached"); synchronized (this) { UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null && device.equals(reader.getDevice())) { // Close reader Log.d(TAG, "Closing reader " + reader.getReaderName()); new CloseTask().execute(); } } } } }; private class OpenTask extends AsyncTask<UsbDevice, Void, Exception> { @Override protected Exception doInBackground(UsbDevice... params) { Exception result = null; stopDetectingReader(); try { String name = params[0].getDeviceName(); Log.d(TAG, "Opening reader " + params[0].getDeviceName()); reader.open(params[0]); readerOpen = true; Log.d(TAG, "Opened reader " + name); startReceivingUsbDeviceDetachBroadcasts(); setNfcReaderStatus(NfcReader.READER_STATUS_OK, null); ACRCommands acrCommands = getReaderCommands(); binder.setReaderTechnology(new ACRReaderTechnology(acrCommands)); int protocol = reader.getProtocol(0); Log.d(TAG, "Protocol is " + protocol); nfcReaderServiceListener.onReaderOpen(acrCommands, NfcReader.READER_STATUS_OK); synchronized (AbstractBackgroundUsbService.this) { requestPermissionDevices.remove(params[0].getDeviceId()); } } catch (Exception e) { Log.w(TAG, "Problem opening reader " + params[0].getDeviceName(), e); synchronized (AbstractBackgroundUsbService.this) { if (e instanceof IllegalArgumentException && e.getMessage().contains("Cannot claim interface.")) { Log.d(TAG, "Fail USB open, attemp to connect " + params[0].getDeviceId() + " again after a delay"); try { Thread.sleep(1000); } catch (InterruptedException e1) { } requestPermissionDevices.remove(params[0].getDeviceId()); } openDevices.remove(params[0].getDeviceId()); } result = e; startDetectingReader(); int status; if (e instanceof IllegalArgumentException && e.getMessage().contains("Cannot claim interface.")) { status = NfcReader.READER_STATUS_ERROR_UNABLE_TO_CLAIM_USB_INTERFACE; } else { status = NfcReader.READER_STATUS_ERROR; } setNfcReaderStatus(status, result.toString()); nfcReaderServiceListener.onReaderClosed(nfcReaderStatusCode, nfcReaderStatusMessage); } return result; } @Override protected void onPostExecute(Exception result) { onOpenACR(result == null); } } public static byte[] passthrough(byte[] payload) { byte[] cmd = new byte[payload.length + 5]; cmd[0] = (byte)0xff; cmd[1] = 0x0; cmd[2] = 0x0; cmd[3] = 0x0; cmd[4] = (byte)(payload.length & 0xFF); System.arraycopy(payload, 0, cmd, 5, payload.length); return cmd; } private class CloseTask extends AsyncTask<Void, Void, Exception> { @Override protected Exception doInBackground(Void... params) { Exception result = null; try { readerOpen = false; UsbDevice device = reader.getDevice(); if (device != null) { synchronized (AbstractBackgroundUsbService.this) { openDevices.remove(device.getDeviceId()); } } reader.close(); } catch (Exception e) { result = e; } finally { stopReceivingUsbDeviceDetachBroadcasts(); setNfcReaderStatus(NfcReader.READER_STATUS_USB_DEVICE_DISCONNECTED, null); nfcReaderServiceListener.onReaderClosed(NfcReader.READER_STATUS_USB_DEVICE_DISCONNECTED, null); } return result; } @Override protected void onPostExecute(Exception result) { onCloseACR(result == null); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(AbstractBackgroundUsbService.this); boolean autoStop = prefs.getBoolean(PREFERENCE_AUTO_STOP_ON_READER_DISCONNECT, false); if (autoStop) { Log.d(TAG, "Auto stop on reader disconnect"); stopSelf(); } else { startDetectingReader(); } } } private static final String ACTION_USB_PERMISSION = AbstractBackgroundUsbService.class.getPackage() + ".USB_PERMISSION"; protected static final String[] stateStrings = {"Unknown", "Absent", "Present", "Swallowed", "Powered", "Negotiable", "Specific"}; protected UsbManager mManager; protected ReaderWrapper reader; protected PendingIntent mPermissionIntent; private Scanner readerScanner; private boolean scanningForReader = false; private boolean recievingDetachBroadcasts = false; protected boolean detectReader = false; private Set<Integer> refusedPermissionDevices = new HashSet<Integer>(); private Set<Integer> requestPermissionDevices = new HashSet<Integer>(); private Set<Integer> openDevices = new HashSet<Integer>(); private IAcr122UBinder acr122Binder; private IAcr1222LBinder acr1222Binder; private IAcr1251UBinder acr1251Binder; private IAcr1281UBinder acr1281Binder; private IAcr1283Binder acr1283Binder; private IAcr1252UBinder acr1252Binder; private IAcr1255UBinder acr1255Binder; @Override public void onCreate() { super.onCreate(); Log.i(TAG, "Service created"); this.acr122Binder = new IAcr122UBinder(); this.acr1222Binder = new IAcr1222LBinder(); this.acr1251Binder = new IAcr1251UBinder(); this.acr1281Binder = new IAcr1281UBinder(); this.acr1283Binder = new IAcr1283Binder(); this.acr1252Binder = new IAcr1252UBinder(); this.acr1255Binder = new IAcr1255UBinder(); nfcReaderServiceListener = new DefaultNfcReaderServiceListener(acr122Binder, acr1222Binder, acr1251Binder, acr1281Binder, acr1283Binder, acr1252Binder, acr1255Binder, this); initialize(); nfcReaderServiceListener.onServiceStarted(); } protected boolean detectUSBDevices() { Log.d(TAG, "Detecing USB devices.."); for (UsbDevice device : mManager.getDeviceList().values()) { if (reader.isSupported(device)) { //askingForPermission = true; Integer deviceId = device.getDeviceId(); synchronized (this) { if (openDevices.contains(deviceId)) { Log.d(TAG, "Device " + deviceId + " is already open"); } else { if (mManager.hasPermission(device)) { Log.d(TAG, "Already has permission for reader: " + device.getDeviceName()); openDevices.add(deviceId); new OpenTask().execute(device); return true; } else { if (!requestPermissionDevices.contains(deviceId)) { requestPermissionDevices.add(deviceId); mManager.requestPermission(device, mPermissionIntent); Log.d(TAG, "Detected ACR reader.."); return true; } else { Log.d(TAG, "Do not ask for permission for previous device " + device.getDeviceName() + " / " + device.getDeviceId()); } } } } } else { Log.d(TAG, "Reader not supported: " + device.getDeviceName()); } } return false; } public boolean isDetectUSBDevice() { return detectReader; } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "Start command"); /* Calendar calendar = Calendar.getInstance(); calendar.set(2017, Calendar.APRIL, 1); if(System.currentTimeMillis() > calendar.getTime().getTime()) { return Service.START_STICKY; } */ if (!started) { // the client app might autostart the service, but it might already be running started = true; startDetectingReader(); } return Service.START_STICKY; } protected void initialize() { // Get USB manager mManager = (UsbManager) getSystemService(Context.USB_SERVICE); // Initialize reader reader = new ReaderWrapper(mManager); reader.setOnStateChangeListener(new OnStateChangeListener() { @Override public void onStateChange(int slot, int prevState, int currState) { // Log.d(TAG, "From state " + prevState + " to " + currState); if (prevState < Reader.CARD_UNKNOWN || prevState > Reader.CARD_SPECIFIC) { prevState = Reader.CARD_UNKNOWN; } if (currState < Reader.CARD_UNKNOWN || currState > Reader.CARD_SPECIFIC) { currState = Reader.CARD_UNKNOWN; } if (prevState == Reader.CARD_ABSENT && currState == Reader.CARD_PRESENT) { //Log.v(TAG, "Tag present on reader"); onTagPresent(slot); } else if (currState == Reader.CARD_ABSENT) { //Log.v(TAG, "Tag absent on reader"); onTagAbsent(slot); } else { Log.d(TAG, "Not action for state transition from " + stateStrings[prevState] + " to " + stateStrings[currState]); } } }); // Register receiver for USB permission mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); readerScanner = new Scanner(this); } private class InitTagTask extends AsyncTask<Integer, Void, Exception> { @Override protected Exception doInBackground(Integer... params) { Exception result = null; int slotNumber = params[0]; try { //Log.i(TAG, "Init tag at slot " + slotNumber); // https://en.wikipedia.org/wiki/Answer_to_reset#General_structure // http://smartcard-atr.appspot.com byte[] atr = reader.power(slotNumber, Reader.CARD_WARM_RESET); if (atr == null) { Log.d(TAG, "No ATR, ignoring"); return null; } final TagType tagType; if (atr != null) { tagType = ServiceUtil.identifyTagType(reader.getReaderName(), atr); } else { tagType = TagType.UNKNOWN; } Log.d(TAG, "Tag inited as " + tagType + " for ATR " + Utils.toHexString(atr)); handleTagInit(slotNumber, atr, tagType); } catch (RemovedCardException e) { Log.d(TAG, "Tag removed before it could be powered; ignore.", e); } catch (Exception e) { Log.w(TAG, "Problem initiating tag", e); ServiceUtil.sendTechBroadcast(AbstractBackgroundUsbService.this); } return result; } @Override protected void onPostExecute(Exception result) { /* * Intent intent = new Intent(); * intent.setAction("de.vogella.android.mybroadcast"); * sendBroadcast(intent); */ } } public abstract void handleTagInit(int slotNumber, byte[] atr, TagType tagType) throws ReaderException; public void onCloseACR(boolean success) { Log.i(TAG, "onCloseACR"); binder.setReaderTechnology(null); } public void onOpenACR(boolean success) { Log.i(TAG, "onOpenACR"); } public void onTagPresent(int slot) { //Log.d(TAG, "onTagPresent"); operations = null; new InitTagTask().execute(slot); } public void onTagAbsent(int slot) { Log.i(TAG, "onTagAbsent"); store.removeItem(slot); Intent intent = new Intent(); intent.setAction(NfcTag.ACTION_TAG_LEFT_FIELD); intent.putExtra(NfcTag.EXTRA_TAG_SERVICE_HANDLE, slot); sendBroadcast(intent); operations = null; } private void startReceivingPermissionBroadcasts(boolean delay) { synchronized (this) { if (!scanningForReader) { Log.d(TAG, "Start scanning for reader"); scanningForReader = true; // register receiver IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_USB_PERMISSION); registerReceiver(usbDevicePermissionReceiver, filter); if (!delay) { readerScanner.resume(); } else { readerScanner.resumeDelayed(); } } } } private void stopReceivingPermissionBroadcasts() { synchronized (this) { if (scanningForReader) { Log.d(TAG, "Stop scanning for reader"); scanningForReader = false; readerScanner.pause(); try { unregisterReceiver(usbDevicePermissionReceiver); } catch (IllegalArgumentException e) { // ignore } } } } protected void startDetectingReader() { synchronized (this) { if (!detectReader) { Log.d(TAG, "Start / resume detecting readers"); detectReader = true; startReceivingPermissionBroadcasts(false); } } } protected void stopDetectingReader() { synchronized (this) { if (detectReader) { Log.d(TAG, "Stop / pause detecting readers"); detectReader = false; stopReceivingPermissionBroadcasts(); } } } private void stopReceivingUsbDeviceDetachBroadcasts() { synchronized (this) { if (recievingDetachBroadcasts) { Log.d(TAG, "Stop recieving USB device detach broadcasts"); recievingDetachBroadcasts = false; // Unregister receiver try { unregisterReceiver(usbDeviceDetachedReceiver); } catch (IllegalArgumentException e) { // ignore } } } } private void startReceivingUsbDeviceDetachBroadcasts() { synchronized (this) { if (!recievingDetachBroadcasts) { Log.d(TAG, "Start recieving USB device detach broadcasts"); recievingDetachBroadcasts = true; // register receiver IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); registerReceiver(usbDeviceDetachedReceiver, filter); } } } @Override public void onDestroy() { detectReader = false; stopReceivingPermissionBroadcasts(); stopReceivingUsbDeviceDetachBroadcasts(); stopReceivingStatusBroadcasts(); // Close reader if (reader != null) { try { if (readerOpen) { setNfcReaderStatus(NfcReader.READER_STATUS_SERVICE_STOPPED, null); synchronized (AbstractBackgroundUsbService.this) { nfcReaderServiceListener.onReaderClosed(nfcReaderStatusCode, nfcReaderStatusMessage); } reader.close(); } } catch (Exception e) { // ignore Log.d(TAG, "Problem closing reader", e); } finally { } } nfcReaderServiceListener.onServiceStopped(); Log.i(TAG, "Service destroyed"); super.onDestroy(); } public void setNfcReaderStatus(int nfcReaderStatusCode, String nfcReaderStatusMessage) { synchronized (AbstractBackgroundUsbService.this) { this.nfcReaderStatusCode = nfcReaderStatusCode; this.nfcReaderStatusMessage = nfcReaderStatusMessage; } } public NdefOperations getNdefOperations() { return operations; } public ACRCommands getReaderCommands() { String name = reader.getReaderName(); if (name != null) { if (name.contains("1222L")) { return new ACR1222Commands(name, reader); } else if (name.contains("122U")) { return new ACR122Commands(name, reader); } else if (name.contains("1251")) { return new ACR1251Commands(name, reader); } else if (name.contains("1281")) { return new ACR1281Commands(name, reader); } else if (name.contains("1283")) { return new ACR1283Commands(name, reader); } else if (name.contains("1252")) { return new ACR1252Commands(name, reader); } else if (name.contains("1255")) { return new ACR1255UsbCommands(name, reader); } else { Log.d(TAG, "No reader control for " + name); } } return new ACRCommands(reader); } }