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 
HostEmulationManager(Context context, RegisteredAidCache aidCache)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 
onPreferredPaymentServiceChanged(ComponentName service)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 
onPreferredForegroundServiceChanged(ComponentName service)118      public void onPreferredForegroundServiceChanged(ComponentName service) {
119          synchronized (mLock) {
120             if (service != null) {
121                bindServiceIfNeededLocked(service);
122             } else {
123                unbindServiceIfNeededLocked();
124             }
125          }
126      }
127 
onHostEmulationActivated()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 
onHostEmulationData(byte[] data)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 
onHostEmulationDeactivated()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 
onOffHostAidSelected()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 
bindServiceIfNeededLocked(ComponentName service)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 
sendDataToServiceLocked(Messenger service, byte[] data)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 
sendDeactivateToActiveServiceLocked(int reason)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 
unbindPaymentServiceLocked()339     void unbindPaymentServiceLocked() {
340         if (mPaymentServiceBound) {
341             mContext.unbindService(mPaymentConnection);
342             mPaymentServiceBound = false;
343             mPaymentService = null;
344             mPaymentServiceName = null;
345         }
346     }
347 
bindPaymentServiceLocked(int userId, ComponentName service)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 
unbindServiceIfNeededLocked()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 
launchTapAgain(ApduServiceInfo service, String category)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 
launchResolver(ArrayList<ApduServiceInfo> services, ComponentName failedComponent, String category)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 
findSelectAid(byte[] data)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
handleMessage(Message msg)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 
bytesToString(byte[] bytes, int offset, int length)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 
dump(FileDescriptor fd, PrintWriter pw, String[] args)517     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
518         pw.println("Bound HCE-A/HCE-B services: ");
519         if (mPaymentServiceBound) {
520             pw.println("    payment: " + mPaymentServiceName);
521         }
522         if (mServiceBound) {
523             pw.println("    other: " + mServiceName);
524         }
525     }
526 }
527