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