Home | History | Annotate | Download | only in cardemulation
      1 /*
      2  * Copyright (C) 2013 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.app.ActivityManager;
     20 import android.app.KeyguardManager;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.nfc.cardemulation.ApduServiceInfo;
     26 import android.nfc.cardemulation.CardEmulation;
     27 import android.nfc.cardemulation.HostApduService;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.os.IBinder;
     31 import android.os.Message;
     32 import android.os.Messenger;
     33 import android.os.RemoteException;
     34 import android.os.UserHandle;
     35 import android.util.Log;
     36 
     37 import com.android.nfc.NfcService;
     38 import com.android.nfc.cardemulation.RegisteredAidCache.AidResolveInfo;
     39 
     40 import java.io.FileDescriptor;
     41 import java.io.PrintWriter;
     42 import java.util.ArrayList;
     43 
     44 public class HostEmulationManager {
     45     static final String TAG = "HostEmulationManager";
     46     static final boolean DBG = false;
     47 
     48     static final int STATE_IDLE = 0;
     49     static final int STATE_W4_SELECT = 1;
     50     static final int STATE_W4_SERVICE = 2;
     51     static final int STATE_W4_DEACTIVATE = 3;
     52     static final int STATE_XFER = 4;
     53 
     54     /** Minimum AID lenth as per ISO7816 */
     55     static final int MINIMUM_AID_LENGTH = 5;
     56 
     57     /** Length of Select APDU header including length byte */
     58     static final int SELECT_APDU_HDR_LENGTH = 5;
     59 
     60     static final byte INSTR_SELECT = (byte)0xA4;
     61 
     62     static final String ANDROID_HCE_AID = "A000000476416E64726F6964484345";
     63     static final byte[] ANDROID_HCE_RESPONSE = {0x14, (byte)0x81, 0x00, 0x00, (byte)0x90, 0x00};
     64 
     65     static final byte[] AID_NOT_FOUND = {0x6A, (byte)0x82};
     66     static final byte[] UNKNOWN_ERROR = {0x6F, 0x00};
     67 
     68     final Context mContext;
     69     final RegisteredAidCache mAidCache;
     70     final Messenger mMessenger = new Messenger (new MessageHandler());
     71     final KeyguardManager mKeyguard;
     72     final Object mLock;
     73 
     74     // All variables below protected by mLock
     75 
     76     // Variables below are for a non-payment service,
     77     // that is typically only bound in the STATE_XFER state.
     78     Messenger mService;
     79     boolean mServiceBound;
     80     ComponentName mServiceName;
     81 
     82     // Variables below are for a payment service,
     83     // which is typically bound persistently to improve on
     84     // latency.
     85     Messenger mPaymentService;
     86     boolean mPaymentServiceBound;
     87     ComponentName mPaymentServiceName;
     88 
     89     // mActiveService denotes the service interface
     90     // that is the current active one, until a new SELECT AID
     91     // comes in that may be resolved to a different service.
     92     // On deactivation, mActiveService stops being valid.
     93     Messenger mActiveService;
     94     ComponentName mActiveServiceName;
     95 
     96     String mLastSelectedAid;
     97     int mState;
     98     byte[] mSelectApdu;
     99 
    100     public HostEmulationManager(Context context, RegisteredAidCache aidCache) {
    101         mContext = context;
    102         mLock = new Object();
    103         mAidCache = aidCache;
    104         mState = STATE_IDLE;
    105         mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
    106     }
    107 
    108     public void onPreferredPaymentServiceChanged(ComponentName service) {
    109         synchronized (mLock) {
    110             if (service != null) {
    111                 bindPaymentServiceLocked(ActivityManager.getCurrentUser(), service);
    112             } else {
    113                 unbindPaymentServiceLocked();
    114             }
    115         }
    116      }
    117 
    118      public void onPreferredForegroundServiceChanged(ComponentName service) {
    119          synchronized (mLock) {
    120             if (service != null) {
    121                bindServiceIfNeededLocked(service);
    122             } else {
    123                unbindServiceIfNeededLocked();
    124             }
    125          }
    126      }
    127 
    128     public void onHostEmulationActivated() {
    129         Log.d(TAG, "notifyHostEmulationActivated");
    130         synchronized (mLock) {
    131             // Regardless of what happens, if we're having a tap again
    132             // activity up, close it
    133             Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE);
    134             intent.setPackage("com.android.nfc");
    135             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
    136             if (mState != STATE_IDLE) {
    137                 Log.e(TAG, "Got activation event in non-idle state");
    138             }
    139             mState = STATE_W4_SELECT;
    140         }
    141     }
    142 
    143     public void onHostEmulationData(byte[] data) {
    144         Log.d(TAG, "notifyHostEmulationData");
    145         String selectAid = findSelectAid(data);
    146         ComponentName resolvedService = null;
    147         synchronized (mLock) {
    148             if (mState == STATE_IDLE) {
    149                 Log.e(TAG, "Got data in idle state.");
    150                 return;
    151             } else if (mState == STATE_W4_DEACTIVATE) {
    152                 Log.e(TAG, "Dropping APDU in STATE_W4_DECTIVATE");
    153                 return;
    154             }
    155             if (selectAid != null) {
    156                 if (selectAid.equals(ANDROID_HCE_AID)) {
    157                     NfcService.getInstance().sendData(ANDROID_HCE_RESPONSE);
    158                     return;
    159                 }
    160                 AidResolveInfo resolveInfo = mAidCache.resolveAid(selectAid);
    161                 if (resolveInfo == null || resolveInfo.services.size() == 0) {
    162                     // Tell the remote we don't handle this AID
    163                     NfcService.getInstance().sendData(AID_NOT_FOUND);
    164                     return;
    165                 }
    166                 mLastSelectedAid = selectAid;
    167                 if (resolveInfo.defaultService != null) {
    168                     // Resolve to default
    169                     // Check if resolvedService requires unlock
    170                     ApduServiceInfo defaultServiceInfo = resolveInfo.defaultService;
    171                     if (defaultServiceInfo.requiresUnlock() &&
    172                             mKeyguard.isKeyguardLocked() && mKeyguard.isKeyguardSecure()) {
    173                         // Just ignore all future APDUs until next tap
    174                         mState = STATE_W4_DEACTIVATE;
    175                         launchTapAgain(resolveInfo.defaultService, resolveInfo.category);
    176                         return;
    177                     }
    178                     // In no circumstance should this be an OffHostService -
    179                     // we should never get this AID on the host in the first place
    180                     if (!defaultServiceInfo.isOnHost()) {
    181                         Log.e(TAG, "AID that was meant to go off-host was routed to host." +
    182                                 " Check routing table configuration.");
    183                         NfcService.getInstance().sendData(AID_NOT_FOUND);
    184                         return;
    185                     }
    186                     resolvedService = defaultServiceInfo.getComponent();
    187                 } else if (mActiveServiceName != null) {
    188                     for (ApduServiceInfo serviceInfo : resolveInfo.services) {
    189                         if (mActiveServiceName.equals(serviceInfo.getComponent())) {
    190                             resolvedService = mActiveServiceName;
    191                             break;
    192                         }
    193                     }
    194                 }
    195                 if (resolvedService == null) {
    196                     // We have no default, and either one or more services.
    197                     // Ask the user to confirm.
    198                     // Just ignore all future APDUs until we resolve to only one
    199                     mState = STATE_W4_DEACTIVATE;
    200                     launchResolver((ArrayList<ApduServiceInfo>)resolveInfo.services, null,
    201                             resolveInfo.category);
    202                     return;
    203                 }
    204             }
    205             switch (mState) {
    206             case STATE_W4_SELECT:
    207                 if (selectAid != null) {
    208                     Messenger existingService = bindServiceIfNeededLocked(resolvedService);
    209                     if (existingService != null) {
    210                         Log.d(TAG, "Binding to existing service");
    211                         mState = STATE_XFER;
    212                         sendDataToServiceLocked(existingService, data);
    213                     } else {
    214                         // Waiting for service to be bound
    215                         Log.d(TAG, "Waiting for new service.");
    216                         // Queue SELECT APDU to be used
    217                         mSelectApdu = data;
    218                         mState = STATE_W4_SERVICE;
    219                     }
    220                 } else {
    221                     Log.d(TAG, "Dropping non-select APDU in STATE_W4_SELECT");
    222                     NfcService.getInstance().sendData(UNKNOWN_ERROR);
    223                 }
    224                 break;
    225             case STATE_W4_SERVICE:
    226                 Log.d(TAG, "Unexpected APDU in STATE_W4_SERVICE");
    227                 break;
    228             case STATE_XFER:
    229                 if (selectAid != null) {
    230                     Messenger existingService = bindServiceIfNeededLocked(resolvedService);
    231                     if (existingService != null) {
    232                         sendDataToServiceLocked(existingService, data);
    233                         mState = STATE_XFER;
    234                     } else {
    235                         // Waiting for service to be bound
    236                         mSelectApdu = data;
    237                         mState = STATE_W4_SERVICE;
    238                     }
    239                 } else if (mActiveService != null) {
    240                     // Regular APDU data
    241                     sendDataToServiceLocked(mActiveService, data);
    242                 } else {
    243                     // No SELECT AID and no active service.
    244                     Log.d(TAG, "Service no longer bound, dropping APDU");
    245                 }
    246                 break;
    247             }
    248         }
    249     }
    250 
    251     public void onHostEmulationDeactivated() {
    252         Log.d(TAG, "notifyHostEmulationDeactivated");
    253         synchronized (mLock) {
    254             if (mState == STATE_IDLE) {
    255                 Log.e(TAG, "Got deactivation event while in idle state");
    256             }
    257             sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_LINK_LOSS);
    258             mActiveService = null;
    259             mActiveServiceName = null;
    260             unbindServiceIfNeededLocked();
    261             mState = STATE_IDLE;
    262         }
    263     }
    264 
    265     public void onOffHostAidSelected() {
    266         Log.d(TAG, "notifyOffHostAidSelected");
    267         synchronized (mLock) {
    268             if (mState != STATE_XFER || mActiveService == null) {
    269                 // Don't bother telling, we're not bound to any service yet
    270             } else {
    271                 sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED);
    272             }
    273             mActiveService = null;
    274             mActiveServiceName = null;
    275             unbindServiceIfNeededLocked();
    276             mState = STATE_W4_SELECT;
    277 
    278             //close the TapAgainDialog
    279             Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE);
    280             intent.setPackage("com.android.nfc");
    281             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
    282         }
    283     }
    284 
    285     Messenger bindServiceIfNeededLocked(ComponentName service) {
    286         if (mPaymentServiceBound && mPaymentServiceName.equals(service)) {
    287             Log.d(TAG, "Service already bound as payment service.");
    288             return mPaymentService;
    289         } else if (mServiceBound && mServiceName.equals(service)) {
    290             Log.d(TAG, "Service already bound as regular service.");
    291             return mService;
    292         } else {
    293             Log.d(TAG, "Binding to service " + service);
    294             unbindServiceIfNeededLocked();
    295             Intent aidIntent = new Intent(HostApduService.SERVICE_INTERFACE);
    296             aidIntent.setComponent(service);
    297             if (mContext.bindServiceAsUser(aidIntent, mConnection,
    298                     Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
    299             } else {
    300                 Log.e(TAG, "Could not bind service.");
    301             }
    302             return null;
    303         }
    304     }
    305 
    306     void sendDataToServiceLocked(Messenger service, byte[] data) {
    307         if (service != mActiveService) {
    308             sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED);
    309             mActiveService = service;
    310             if (service.equals(mPaymentService)) {
    311                 mActiveServiceName = mPaymentServiceName;
    312             } else {
    313                 mActiveServiceName = mServiceName;
    314             }
    315         }
    316         Message msg = Message.obtain(null, HostApduService.MSG_COMMAND_APDU);
    317         Bundle dataBundle = new Bundle();
    318         dataBundle.putByteArray("data", data);
    319         msg.setData(dataBundle);
    320         msg.replyTo = mMessenger;
    321         try {
    322             mActiveService.send(msg);
    323         } catch (RemoteException e) {
    324             Log.e(TAG, "Remote service has died, dropping APDU");
    325         }
    326     }
    327 
    328     void sendDeactivateToActiveServiceLocked(int reason) {
    329         if (mActiveService == null) return;
    330         Message msg = Message.obtain(null, HostApduService.MSG_DEACTIVATED);
    331         msg.arg1 = reason;
    332         try {
    333             mActiveService.send(msg);
    334         } catch (RemoteException e) {
    335             // Don't care
    336         }
    337     }
    338 
    339     void unbindPaymentServiceLocked() {
    340         if (mPaymentServiceBound) {
    341             mContext.unbindService(mPaymentConnection);
    342             mPaymentServiceBound = false;
    343             mPaymentService = null;
    344             mPaymentServiceName = null;
    345         }
    346     }
    347 
    348     void bindPaymentServiceLocked(int userId, ComponentName service) {
    349         unbindPaymentServiceLocked();
    350 
    351         Intent intent = new Intent(HostApduService.SERVICE_INTERFACE);
    352         intent.setComponent(service);
    353         if (!mContext.bindServiceAsUser(intent, mPaymentConnection,
    354                 Context.BIND_AUTO_CREATE, new UserHandle(userId))) {
    355             Log.e(TAG, "Could not bind (persistent) payment service.");
    356         }
    357     }
    358 
    359     void unbindServiceIfNeededLocked() {
    360         if (mServiceBound) {
    361             Log.d(TAG, "Unbinding from service " + mServiceName);
    362             mContext.unbindService(mConnection);
    363             mServiceBound = false;
    364             mService = null;
    365             mServiceName = null;
    366         }
    367     }
    368 
    369     void launchTapAgain(ApduServiceInfo service, String category) {
    370         Intent dialogIntent = new Intent(mContext, TapAgainDialog.class);
    371         dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, category);
    372         dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, service);
    373         dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    374         mContext.startActivityAsUser(dialogIntent, UserHandle.CURRENT);
    375     }
    376 
    377     void launchResolver(ArrayList<ApduServiceInfo> services, ComponentName failedComponent,
    378             String category) {
    379         Intent intent = new Intent(mContext, AppChooserActivity.class);
    380         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    381         intent.putParcelableArrayListExtra(AppChooserActivity.EXTRA_APDU_SERVICES, services);
    382         intent.putExtra(AppChooserActivity.EXTRA_CATEGORY, category);
    383         if (failedComponent != null) {
    384             intent.putExtra(AppChooserActivity.EXTRA_FAILED_COMPONENT, failedComponent);
    385         }
    386         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
    387     }
    388 
    389     String findSelectAid(byte[] data) {
    390         if (data == null || data.length < SELECT_APDU_HDR_LENGTH + MINIMUM_AID_LENGTH) {
    391             if (DBG) Log.d(TAG, "Data size too small for SELECT APDU");
    392             return null;
    393         }
    394         // To accept a SELECT AID for dispatch, we require the following:
    395         // Class byte must be 0x00: logical channel set to zero, no secure messaging, no chaining
    396         // Instruction byte must be 0xA4: SELECT instruction
    397         // P1: must be 0x04: select by application identifier
    398         // P2: File control information is only relevant for higher-level application,
    399         //     and we only support "first or only occurrence".
    400         if (data[0] == 0x00 && data[1] == INSTR_SELECT && data[2] == 0x04) {
    401             if (data[3] != 0x00) {
    402                 Log.d(TAG, "Selecting next, last or previous AID occurrence is not supported");
    403             }
    404             int aidLength = data[4];
    405             if (data.length < SELECT_APDU_HDR_LENGTH + aidLength) {
    406                 return null;
    407             }
    408             return bytesToString(data, SELECT_APDU_HDR_LENGTH, aidLength);
    409         }
    410         return null;
    411     }
    412 
    413     private ServiceConnection mPaymentConnection = new ServiceConnection() {
    414         @Override
    415         public void onServiceConnected(ComponentName name, IBinder service) {
    416             synchronized (mLock) {
    417                 mPaymentServiceName = name;
    418                 mPaymentService = new Messenger(service);
    419                 mPaymentServiceBound = true;
    420             }
    421         }
    422 
    423         @Override
    424         public void onServiceDisconnected(ComponentName name) {
    425             synchronized (mLock) {
    426                 mPaymentService = null;
    427                 mPaymentServiceBound = false;
    428                 mPaymentServiceName = null;
    429             }
    430         }
    431     };
    432 
    433     private ServiceConnection mConnection = new ServiceConnection() {
    434         @Override
    435         public void onServiceConnected(ComponentName name, IBinder service) {
    436             synchronized (mLock) {
    437                 mService = new Messenger(service);
    438                 mServiceBound = true;
    439                 mServiceName = name;
    440                 Log.d(TAG, "Service bound");
    441                 mState = STATE_XFER;
    442                 // Send pending select APDU
    443                 if (mSelectApdu != null) {
    444                     sendDataToServiceLocked(mService, mSelectApdu);
    445                     mSelectApdu = null;
    446                 }
    447             }
    448         }
    449 
    450         @Override
    451         public void onServiceDisconnected(ComponentName name) {
    452             synchronized (mLock) {
    453                 Log.d(TAG, "Service unbound");
    454                 mService = null;
    455                 mServiceBound = false;
    456             }
    457         }
    458     };
    459 
    460     class MessageHandler extends Handler {
    461         @Override
    462         public void handleMessage(Message msg) {
    463             synchronized(mLock) {
    464                 if (mActiveService == null) {
    465                     Log.d(TAG, "Dropping service response message; service no longer active.");
    466                     return;
    467                 } else if (!msg.replyTo.getBinder().equals(mActiveService.getBinder())) {
    468                     Log.d(TAG, "Dropping service response message; service no longer bound.");
    469                     return;
    470                 }
    471             }
    472             if (msg.what == HostApduService.MSG_RESPONSE_APDU) {
    473                 Bundle dataBundle = msg.getData();
    474                 if (dataBundle == null) {
    475                     return;
    476                 }
    477                 byte[] data = dataBundle.getByteArray("data");
    478                 if (data == null || data.length == 0) {
    479                     Log.e(TAG, "Dropping empty R-APDU");
    480                     return;
    481                 }
    482                 int state;
    483                 synchronized(mLock) {
    484                     state = mState;
    485                 }
    486                 if (state == STATE_XFER) {
    487                     Log.d(TAG, "Sending data");
    488                     NfcService.getInstance().sendData(data);
    489                 } else {
    490                     Log.d(TAG, "Dropping data, wrong state " + Integer.toString(state));
    491                 }
    492             } else if (msg.what == HostApduService.MSG_UNHANDLED) {
    493                 synchronized (mLock) {
    494                     AidResolveInfo resolveInfo = mAidCache.resolveAid(mLastSelectedAid);
    495                     boolean isPayment = false;
    496                     if (resolveInfo.services.size() > 0) {
    497                         launchResolver((ArrayList<ApduServiceInfo>)resolveInfo.services,
    498                                 mActiveServiceName, resolveInfo.category);
    499                     }
    500                 }
    501             }
    502         }
    503     }
    504 
    505     static String bytesToString(byte[] bytes, int offset, int length) {
    506         final char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    507         char[] chars = new char[length * 2];
    508         int byteValue;
    509         for (int j = 0; j < length; j++) {
    510             byteValue = bytes[offset + j] & 0xFF;
    511             chars[j * 2] = hexChars[byteValue >>> 4];
    512             chars[j * 2 + 1] = hexChars[byteValue & 0x0F];
    513         }
    514         return new String(chars);
    515     }
    516 
    517     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    518         pw.println("Bound services: ");
    519         if (mPaymentServiceBound) {
    520             pw.println("    payment: " + mPaymentServiceName);
    521         }
    522         if (mServiceBound) {
    523             pw.println("    other: " + mServiceName);
    524         }
    525     }
    526 }
    527