Home | History | Annotate | Download | only in cardemulation
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.nfc.cardemulation;
     18 
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.ServiceConnection;
     23 import android.nfc.cardemulation.NfcFServiceInfo;
     24 import android.nfc.cardemulation.HostNfcFService;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.IBinder;
     28 import android.os.Message;
     29 import android.os.Messenger;
     30 import android.os.RemoteException;
     31 import android.os.UserHandle;
     32 import android.util.Log;
     33 
     34 import com.android.nfc.NfcService;
     35 
     36 import java.io.FileDescriptor;
     37 import java.io.PrintWriter;
     38 
     39 public class HostNfcFEmulationManager {
     40     static final String TAG = "HostNfcFEmulationManager";
     41     static final boolean DBG = false;
     42 
     43     static final int STATE_IDLE = 0;
     44     static final int STATE_W4_SERVICE = 1;
     45     static final int STATE_XFER = 2;
     46 
     47     /** NFCID2 length */
     48     static final int NFCID2_LENGTH = 8;
     49 
     50     /** Minimum NFC-F packets including length, command code and NFCID2 */
     51     static final int MINIMUM_NFCF_PACKET_LENGTH = 10;
     52 
     53     final Context mContext;
     54     final RegisteredT3tIdentifiersCache mT3tIdentifiersCache;
     55     final Messenger mMessenger = new Messenger (new MessageHandler());
     56     final Object mLock;
     57 
     58     // All variables below protected by mLock
     59     ComponentName mEnabledFgServiceName;
     60 
     61     Messenger mService;
     62     boolean mServiceBound;
     63     ComponentName mServiceName;
     64 
     65     // mActiveService denotes the service interface
     66     // that is the current active one, until a new packet
     67     // comes in that may be resolved to a different service.
     68     // On deactivation, mActiveService stops being valid.
     69     Messenger mActiveService;
     70     ComponentName mActiveServiceName;
     71 
     72     int mState;
     73     byte[] mPendingPacket;
     74 
     75     public HostNfcFEmulationManager(Context context,
     76             RegisteredT3tIdentifiersCache t3tIdentifiersCache) {
     77         mContext = context;
     78         mLock = new Object();
     79         mEnabledFgServiceName = null;
     80         mT3tIdentifiersCache = t3tIdentifiersCache;
     81         mState = STATE_IDLE;
     82     }
     83 
     84     public void onEnabledForegroundNfcFServiceChanged(ComponentName service) {
     85         synchronized (mLock) {
     86             mEnabledFgServiceName = service;
     87             if (service == null) {
     88                 sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
     89                 unbindServiceIfNeededLocked();
     90             }
     91         }
     92     }
     93 
     94     public void onHostEmulationActivated() {
     95         if (DBG) Log.d(TAG, "notifyHostEmulationActivated");
     96     }
     97 
     98     public void onHostEmulationData(byte[] data) {
     99         if (DBG) Log.d(TAG, "notifyHostEmulationData");
    100         String nfcid2 = findNfcid2(data);
    101         ComponentName resolvedServiceName = null;
    102         synchronized (mLock) {
    103             if (nfcid2 != null) {
    104                 NfcFServiceInfo resolvedService = mT3tIdentifiersCache.resolveNfcid2(nfcid2);
    105                 if (resolvedService != null) {
    106                     resolvedServiceName = resolvedService.getComponent();
    107                 }
    108             }
    109             if (resolvedServiceName == null) {
    110                 if (mActiveServiceName == null) {
    111                     return;
    112                 }
    113                 resolvedServiceName = mActiveServiceName;
    114             }
    115             // Check if resolvedService is actually currently enabled
    116             if (mEnabledFgServiceName == null ||
    117                     !mEnabledFgServiceName.equals(resolvedServiceName)) {
    118                 return;
    119             }
    120             if (DBG) Log.d(TAG, "resolvedServiceName: " + resolvedServiceName.toString() +
    121                     "mState: " + String.valueOf(mState));
    122             switch (mState) {
    123             case STATE_IDLE:
    124                 Messenger existingService = bindServiceIfNeededLocked(resolvedServiceName);
    125                 if (existingService != null) {
    126                     Log.d(TAG, "Binding to existing service");
    127                     mState = STATE_XFER;
    128                     sendDataToServiceLocked(existingService, data);
    129                 } else {
    130                     // Waiting for service to be bound
    131                     Log.d(TAG, "Waiting for new service.");
    132                     // Queue packet to be used
    133                     mPendingPacket = data;
    134                     mState = STATE_W4_SERVICE;
    135                 }
    136                 break;
    137             case STATE_W4_SERVICE:
    138                 Log.d(TAG, "Unexpected packet in STATE_W4_SERVICE");
    139                 break;
    140             case STATE_XFER:
    141                 // Regular packet data
    142                 sendDataToServiceLocked(mActiveService, data);
    143                 break;
    144             }
    145         }
    146     }
    147 
    148     public void onHostEmulationDeactivated() {
    149         if (DBG) Log.d(TAG, "notifyHostEmulationDeactivated");
    150         synchronized (mLock) {
    151             sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
    152             mActiveService = null;
    153             mActiveServiceName = null;
    154             unbindServiceIfNeededLocked();
    155             mState = STATE_IDLE;
    156         }
    157     }
    158 
    159     public void onNfcDisabled() {
    160         synchronized (mLock) {
    161             sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
    162             mEnabledFgServiceName = null;
    163             mActiveService = null;
    164             mActiveServiceName = null;
    165             unbindServiceIfNeededLocked();
    166             mState = STATE_IDLE;
    167         }
    168     }
    169 
    170     public void onUserSwitched() {
    171         synchronized (mLock) {
    172             sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
    173             mEnabledFgServiceName = null;
    174             mActiveService = null;
    175             mActiveServiceName = null;
    176             unbindServiceIfNeededLocked();
    177             mState = STATE_IDLE;
    178         }
    179     }
    180 
    181     void sendDataToServiceLocked(Messenger service, byte[] data) {
    182         if (DBG) Log.d(TAG, "sendDataToServiceLocked");
    183         if (DBG) {
    184             Log.d(TAG, "service: " +
    185                     (service != null ? service.toString() : "null"));
    186             Log.d(TAG, "mActiveService: " +
    187                     (mActiveService != null ? mActiveService.toString() : "null"));
    188         }
    189         if (service != mActiveService) {
    190             sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
    191             mActiveService = service;
    192             mActiveServiceName = mServiceName;
    193         }
    194         Message msg = Message.obtain(null, HostNfcFService.MSG_COMMAND_PACKET);
    195         Bundle dataBundle = new Bundle();
    196         dataBundle.putByteArray("data", data);
    197         msg.setData(dataBundle);
    198         msg.replyTo = mMessenger;
    199         try {
    200             Log.d(TAG, "Sending data to service");
    201             if (DBG) Log.d(TAG, "data: " + getByteDump(data));
    202             mActiveService.send(msg);
    203         } catch (RemoteException e) {
    204             Log.e(TAG, "Remote service has died, dropping packet");
    205         }
    206     }
    207 
    208     void sendDeactivateToActiveServiceLocked(int reason) {
    209         if (DBG) Log.d(TAG, "sendDeactivateToActiveServiceLocked");
    210         if (mActiveService == null) return;
    211         Message msg = Message.obtain(null, HostNfcFService.MSG_DEACTIVATED);
    212         msg.arg1 = reason;
    213         try {
    214             mActiveService.send(msg);
    215         } catch (RemoteException e) {
    216             // Don't care
    217         }
    218     }
    219 
    220     Messenger bindServiceIfNeededLocked(ComponentName service) {
    221         if (DBG) Log.d(TAG, "bindServiceIfNeededLocked");
    222         if (mServiceBound && mServiceName.equals(service)) {
    223             Log.d(TAG, "Service already bound.");
    224             return mService;
    225         } else {
    226             Log.d(TAG, "Binding to service " + service);
    227             unbindServiceIfNeededLocked();
    228             Intent bindIntent = new Intent(HostNfcFService.SERVICE_INTERFACE);
    229             bindIntent.setComponent(service);
    230             if (mContext.bindServiceAsUser(bindIntent, mConnection,
    231                     Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
    232             } else {
    233                 Log.e(TAG, "Could not bind service.");
    234             }
    235             return null;
    236         }
    237     }
    238 
    239     void unbindServiceIfNeededLocked() {
    240         if (DBG) Log.d(TAG, "unbindServiceIfNeededLocked");
    241         if (mServiceBound) {
    242             Log.d(TAG, "Unbinding from service " + mServiceName);
    243             mContext.unbindService(mConnection);
    244             mServiceBound = false;
    245             mService = null;
    246             mServiceName = null;
    247         }
    248     }
    249 
    250     String findNfcid2(byte[] data) {
    251         if (DBG) Log.d(TAG, "findNfcid2");
    252         if (data == null || data.length < MINIMUM_NFCF_PACKET_LENGTH) {
    253             if (DBG) Log.d(TAG, "Data size too small");
    254             return null;
    255         }
    256         int nfcid2Offset = 2;
    257         return bytesToString(data, nfcid2Offset, NFCID2_LENGTH);
    258     }
    259 
    260     private ServiceConnection mConnection = new ServiceConnection() {
    261         @Override
    262         public void onServiceConnected(ComponentName name, IBinder service) {
    263             synchronized (mLock) {
    264                 mService = new Messenger(service);
    265                 mServiceBound = true;
    266                 mServiceName = name;
    267                 Log.d(TAG, "Service bound");
    268                 mState = STATE_XFER;
    269                 // Send pending packet
    270                 if (mPendingPacket != null) {
    271                     sendDataToServiceLocked(mService, mPendingPacket);
    272                     mPendingPacket = null;
    273                 }
    274             }
    275         }
    276 
    277         @Override
    278         public void onServiceDisconnected(ComponentName name) {
    279             synchronized (mLock) {
    280                 Log.d(TAG, "Service unbound");
    281                 mService = null;
    282                 mServiceBound = false;
    283                 mServiceName = null;
    284             }
    285         }
    286     };
    287 
    288     class MessageHandler extends Handler {
    289         @Override
    290         public void handleMessage(Message msg) {
    291             synchronized(mLock) {
    292                 if (mActiveService == null) {
    293                     Log.d(TAG, "Dropping service response message; service no longer active.");
    294                     return;
    295                 } else if (!msg.replyTo.getBinder().equals(mActiveService.getBinder())) {
    296                     Log.d(TAG, "Dropping service response message; service no longer bound.");
    297                     return;
    298                 }
    299             }
    300             if (msg.what == HostNfcFService.MSG_RESPONSE_PACKET) {
    301                 Bundle dataBundle = msg.getData();
    302                 if (dataBundle == null) {
    303                     return;
    304                 }
    305                 byte[] data = dataBundle.getByteArray("data");
    306                 if (data == null) {
    307                     return;
    308                 }
    309                 if (data.length == 0) {
    310                     Log.e(TAG, "Invalid response packet");
    311                     return;
    312                 }
    313                 if (data.length != (data[0] & 0xff)) {
    314                     Log.e(TAG, "Invalid response packet");
    315                     return;
    316                 }
    317                 int state;
    318                 synchronized(mLock) {
    319                     state = mState;
    320                 }
    321                 if (state == STATE_XFER) {
    322                     Log.d(TAG, "Sending data");
    323                     if (DBG) Log.d(TAG, "data:" + getByteDump(data));
    324                     NfcService.getInstance().sendData(data);
    325                 } else {
    326                     Log.d(TAG, "Dropping data, wrong state " + Integer.toString(state));
    327                 }
    328             }
    329         }
    330     }
    331 
    332     static String bytesToString(byte[] bytes, int offset, int length) {
    333         final char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    334         char[] chars = new char[length * 2];
    335         int byteValue;
    336         for (int j = 0; j < length; j++) {
    337             byteValue = bytes[offset + j] & 0xFF;
    338             chars[j * 2] = hexChars[byteValue >>> 4];
    339             chars[j * 2 + 1] = hexChars[byteValue & 0x0F];
    340         }
    341         return new String(chars);
    342     }
    343 
    344     private String getByteDump(final byte[] cmd) {
    345         StringBuffer str = new StringBuffer("");
    346         int letters = 8;
    347         int i = 0;
    348 
    349         if (cmd == null) {
    350             str.append(" null\n");
    351             return str.toString();
    352         }
    353 
    354         for (; i < cmd.length; i++) {
    355             str.append(String.format(" %02X", cmd[i]));
    356             if ((i % letters == letters - 1) || (i + 1 == cmd.length)) {
    357                 str.append("\n");
    358             }
    359         }
    360 
    361         return str.toString();
    362     }
    363 
    364     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    365         pw.println("Bound HCE-F services: ");
    366         if (mServiceBound) {
    367             pw.println("    service: " + mServiceName);
    368         }
    369     }
    370 }
    371