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