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