| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.nfc.cardemulation; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.nfc.cardemulation.HostNfcFService; |
| import android.nfc.cardemulation.NfcFServiceInfo; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.Messenger; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.util.Log; |
| import com.android.nfc.NfcService; |
| import com.android.nfc.NfcStatsLog; |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| |
| public class HostNfcFEmulationManager { |
| static final String TAG = "HostNfcFEmulationManager"; |
| static final boolean DBG = false; |
| |
| static final int STATE_IDLE = 0; |
| static final int STATE_W4_SERVICE = 1; |
| static final int STATE_XFER = 2; |
| |
| /** NFCID2 length */ |
| static final int NFCID2_LENGTH = 8; |
| |
| /** Minimum NFC-F packets including length, command code and NFCID2 */ |
| static final int MINIMUM_NFCF_PACKET_LENGTH = 10; |
| |
| final Context mContext; |
| final RegisteredT3tIdentifiersCache mT3tIdentifiersCache; |
| final Messenger mMessenger = new Messenger (new MessageHandler()); |
| final Object mLock; |
| |
| // All variables below protected by mLock |
| ComponentName mEnabledFgServiceName; |
| |
| Messenger mService; |
| boolean mServiceBound; |
| ComponentName mServiceName; |
| |
| // mActiveService denotes the service interface |
| // that is the current active one, until a new packet |
| // comes in that may be resolved to a different service. |
| // On deactivation, mActiveService stops being valid. |
| Messenger mActiveService; |
| ComponentName mActiveServiceName; |
| |
| int mState; |
| byte[] mPendingPacket; |
| |
| public HostNfcFEmulationManager(Context context, |
| RegisteredT3tIdentifiersCache t3tIdentifiersCache) { |
| mContext = context; |
| mLock = new Object(); |
| mEnabledFgServiceName = null; |
| mT3tIdentifiersCache = t3tIdentifiersCache; |
| mState = STATE_IDLE; |
| } |
| |
| public void onEnabledForegroundNfcFServiceChanged(ComponentName service) { |
| synchronized (mLock) { |
| mEnabledFgServiceName = service; |
| if (service == null) { |
| sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); |
| unbindServiceIfNeededLocked(); |
| } |
| } |
| } |
| |
| public void onHostEmulationActivated() { |
| if (DBG) Log.d(TAG, "notifyHostEmulationActivated"); |
| } |
| |
| public void onHostEmulationData(byte[] data) { |
| if (DBG) Log.d(TAG, "notifyHostEmulationData"); |
| String nfcid2 = findNfcid2(data); |
| ComponentName resolvedServiceName = null; |
| synchronized (mLock) { |
| if (nfcid2 != null) { |
| NfcFServiceInfo resolvedService = mT3tIdentifiersCache.resolveNfcid2(nfcid2); |
| if (resolvedService != null) { |
| resolvedServiceName = resolvedService.getComponent(); |
| } |
| } |
| if (resolvedServiceName == null) { |
| if (mActiveServiceName == null) { |
| return; |
| } |
| resolvedServiceName = mActiveServiceName; |
| } |
| // Check if resolvedService is actually currently enabled |
| if (mEnabledFgServiceName == null || |
| !mEnabledFgServiceName.equals(resolvedServiceName)) { |
| return; |
| } |
| if (DBG) Log.d(TAG, "resolvedServiceName: " + resolvedServiceName.toString() + |
| "mState: " + String.valueOf(mState)); |
| switch (mState) { |
| case STATE_IDLE: |
| Messenger existingService = bindServiceIfNeededLocked(resolvedServiceName); |
| if (existingService != null) { |
| Log.d(TAG, "Binding to existing service"); |
| mState = STATE_XFER; |
| sendDataToServiceLocked(existingService, data); |
| } else { |
| // Waiting for service to be bound |
| Log.d(TAG, "Waiting for new service."); |
| // Queue packet to be used |
| mPendingPacket = data; |
| mState = STATE_W4_SERVICE; |
| } |
| NfcStatsLog.write(NfcStatsLog.NFC_CARDEMULATION_OCCURRED, |
| NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__HCE_PAYMENT, |
| "HCEF"); |
| break; |
| case STATE_W4_SERVICE: |
| Log.d(TAG, "Unexpected packet in STATE_W4_SERVICE"); |
| break; |
| case STATE_XFER: |
| // Regular packet data |
| sendDataToServiceLocked(mActiveService, data); |
| break; |
| } |
| } |
| } |
| |
| public void onHostEmulationDeactivated() { |
| if (DBG) Log.d(TAG, "notifyHostEmulationDeactivated"); |
| synchronized (mLock) { |
| sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); |
| mActiveService = null; |
| mActiveServiceName = null; |
| unbindServiceIfNeededLocked(); |
| mState = STATE_IDLE; |
| } |
| } |
| |
| public void onNfcDisabled() { |
| synchronized (mLock) { |
| sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); |
| mEnabledFgServiceName = null; |
| mActiveService = null; |
| mActiveServiceName = null; |
| unbindServiceIfNeededLocked(); |
| mState = STATE_IDLE; |
| } |
| } |
| |
| public void onUserSwitched() { |
| synchronized (mLock) { |
| sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); |
| mEnabledFgServiceName = null; |
| mActiveService = null; |
| mActiveServiceName = null; |
| unbindServiceIfNeededLocked(); |
| mState = STATE_IDLE; |
| } |
| } |
| |
| void sendDataToServiceLocked(Messenger service, byte[] data) { |
| if (DBG) Log.d(TAG, "sendDataToServiceLocked"); |
| if (DBG) { |
| Log.d(TAG, "service: " + |
| (service != null ? service.toString() : "null")); |
| Log.d(TAG, "mActiveService: " + |
| (mActiveService != null ? mActiveService.toString() : "null")); |
| } |
| if (service != mActiveService) { |
| sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); |
| mActiveService = service; |
| mActiveServiceName = mServiceName; |
| } |
| Message msg = Message.obtain(null, HostNfcFService.MSG_COMMAND_PACKET); |
| Bundle dataBundle = new Bundle(); |
| dataBundle.putByteArray("data", data); |
| msg.setData(dataBundle); |
| msg.replyTo = mMessenger; |
| try { |
| Log.d(TAG, "Sending data to service"); |
| if (DBG) Log.d(TAG, "data: " + getByteDump(data)); |
| mActiveService.send(msg); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Remote service has died, dropping packet"); |
| } |
| } |
| |
| void sendDeactivateToActiveServiceLocked(int reason) { |
| if (DBG) Log.d(TAG, "sendDeactivateToActiveServiceLocked"); |
| if (mActiveService == null) return; |
| Message msg = Message.obtain(null, HostNfcFService.MSG_DEACTIVATED); |
| msg.arg1 = reason; |
| try { |
| mActiveService.send(msg); |
| } catch (RemoteException e) { |
| // Don't care |
| } |
| } |
| |
| Messenger bindServiceIfNeededLocked(ComponentName service) { |
| if (DBG) Log.d(TAG, "bindServiceIfNeededLocked"); |
| if (mServiceBound && mServiceName.equals(service)) { |
| Log.d(TAG, "Service already bound."); |
| return mService; |
| } else { |
| Log.d(TAG, "Binding to service " + service); |
| unbindServiceIfNeededLocked(); |
| Intent bindIntent = new Intent(HostNfcFService.SERVICE_INTERFACE); |
| bindIntent.setComponent(service); |
| if (mContext.bindServiceAsUser(bindIntent, mConnection, |
| Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) { |
| } else { |
| Log.e(TAG, "Could not bind service."); |
| } |
| return null; |
| } |
| } |
| |
| void unbindServiceIfNeededLocked() { |
| if (DBG) Log.d(TAG, "unbindServiceIfNeededLocked"); |
| if (mServiceBound) { |
| Log.d(TAG, "Unbinding from service " + mServiceName); |
| mContext.unbindService(mConnection); |
| mServiceBound = false; |
| mService = null; |
| mServiceName = null; |
| } |
| } |
| |
| String findNfcid2(byte[] data) { |
| if (DBG) Log.d(TAG, "findNfcid2"); |
| if (data == null || data.length < MINIMUM_NFCF_PACKET_LENGTH) { |
| if (DBG) Log.d(TAG, "Data size too small"); |
| return null; |
| } |
| int nfcid2Offset = 2; |
| return bytesToString(data, nfcid2Offset, NFCID2_LENGTH); |
| } |
| |
| private ServiceConnection mConnection = new ServiceConnection() { |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| synchronized (mLock) { |
| mService = new Messenger(service); |
| mServiceBound = true; |
| mServiceName = name; |
| Log.d(TAG, "Service bound"); |
| mState = STATE_XFER; |
| // Send pending packet |
| if (mPendingPacket != null) { |
| sendDataToServiceLocked(mService, mPendingPacket); |
| mPendingPacket = null; |
| } |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| synchronized (mLock) { |
| Log.d(TAG, "Service unbound"); |
| mService = null; |
| mServiceBound = false; |
| mServiceName = null; |
| } |
| } |
| }; |
| |
| class MessageHandler extends Handler { |
| @Override |
| public void handleMessage(Message msg) { |
| synchronized(mLock) { |
| if (mActiveService == null) { |
| Log.d(TAG, "Dropping service response message; service no longer active."); |
| return; |
| } else if (!msg.replyTo.getBinder().equals(mActiveService.getBinder())) { |
| Log.d(TAG, "Dropping service response message; service no longer bound."); |
| return; |
| } |
| } |
| if (msg.what == HostNfcFService.MSG_RESPONSE_PACKET) { |
| Bundle dataBundle = msg.getData(); |
| if (dataBundle == null) { |
| return; |
| } |
| byte[] data = dataBundle.getByteArray("data"); |
| if (data == null) { |
| return; |
| } |
| if (data.length == 0) { |
| Log.e(TAG, "Invalid response packet"); |
| return; |
| } |
| if (data.length != (data[0] & 0xff)) { |
| Log.e(TAG, "Invalid response packet"); |
| return; |
| } |
| int state; |
| synchronized(mLock) { |
| state = mState; |
| } |
| if (state == STATE_XFER) { |
| Log.d(TAG, "Sending data"); |
| if (DBG) Log.d(TAG, "data:" + getByteDump(data)); |
| NfcService.getInstance().sendData(data); |
| } else { |
| Log.d(TAG, "Dropping data, wrong state " + Integer.toString(state)); |
| } |
| } |
| } |
| } |
| |
| static String bytesToString(byte[] bytes, int offset, int length) { |
| final char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; |
| char[] chars = new char[length * 2]; |
| int byteValue; |
| for (int j = 0; j < length; j++) { |
| byteValue = bytes[offset + j] & 0xFF; |
| chars[j * 2] = hexChars[byteValue >>> 4]; |
| chars[j * 2 + 1] = hexChars[byteValue & 0x0F]; |
| } |
| return new String(chars); |
| } |
| |
| private String getByteDump(final byte[] cmd) { |
| StringBuffer str = new StringBuffer(""); |
| int letters = 8; |
| int i = 0; |
| |
| if (cmd == null) { |
| str.append(" null\n"); |
| return str.toString(); |
| } |
| |
| for (; i < cmd.length; i++) { |
| str.append(String.format(" %02X", cmd[i])); |
| if ((i % letters == letters - 1) || (i + 1 == cmd.length)) { |
| str.append("\n"); |
| } |
| } |
| |
| return str.toString(); |
| } |
| |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("Bound HCE-F services: "); |
| if (mServiceBound) { |
| pw.println(" service: " + mServiceName); |
| } |
| } |
| } |