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