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