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