/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.nfc.cardemulation; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.nfc.INfcCardEmulation; import android.nfc.INfcFCardEmulation; import android.nfc.cardemulation.AidGroup; import android.nfc.cardemulation.ApduServiceInfo; import android.nfc.cardemulation.NfcFServiceInfo; import android.nfc.cardemulation.CardEmulation; import android.nfc.cardemulation.NfcFCardEmulation; import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; import android.os.PowerManager; import android.os.SystemClock; import android.provider.Settings; import android.util.Log; import com.android.nfc.NfcPermissions; import com.android.nfc.NfcService; import com.android.nfc.cardemulation.RegisteredServicesCache; import com.android.nfc.cardemulation.RegisteredNfcFServicesCache; /** * CardEmulationManager is the central entity * responsible for delegating to individual components * implementing card emulation: * - RegisteredServicesCache keeping track of HCE and SE services on the device * - RegisteredNfcFServicesCache keeping track of HCE-F services on the device * - RegisteredAidCache keeping track of AIDs registered by those services and manages * the routing table in the NFCC. * - RegisteredT3tIdentifiersCache keeping track of T3T Identifier registered by * those services and manages the routing table in the NFCC. * - HostEmulationManager handles incoming APDUs for the host and forwards to HCE * services as necessary. * - HostNfcFEmulationManager handles incoming NFC-F packets for the host and * forwards to HCE-F services as necessary. */ public class CardEmulationManager implements RegisteredServicesCache.Callback, RegisteredNfcFServicesCache.Callback, PreferredServices.Callback, EnabledNfcFServices.Callback { static final String TAG = "CardEmulationManager"; static final boolean DBG = false; static final int NFC_HCE_APDU = 0x01; static final int NFC_HCE_NFCF = 0x04; final RegisteredAidCache mAidCache; final RegisteredT3tIdentifiersCache mT3tIdentifiersCache; final RegisteredServicesCache mServiceCache; final RegisteredNfcFServicesCache mNfcFServicesCache; final HostEmulationManager mHostEmulationManager; final HostNfcFEmulationManager mHostNfcFEmulationManager; final PreferredServices mPreferredServices; final EnabledNfcFServices mEnabledNfcFServices; final Context mContext; final CardEmulationInterface mCardEmulationInterface; final NfcFCardEmulationInterface mNfcFCardEmulationInterface; final PowerManager mPowerManager; public CardEmulationManager(Context context) { mContext = context; mCardEmulationInterface = new CardEmulationInterface(); mNfcFCardEmulationInterface = new NfcFCardEmulationInterface(); mAidCache = new RegisteredAidCache(context); mT3tIdentifiersCache = new RegisteredT3tIdentifiersCache(context); mHostEmulationManager = new HostEmulationManager(context, mAidCache); mHostNfcFEmulationManager = new HostNfcFEmulationManager(context, mT3tIdentifiersCache); mServiceCache = new RegisteredServicesCache(context, this); mNfcFServicesCache = new RegisteredNfcFServicesCache(context, this); mPreferredServices = new PreferredServices(context, mServiceCache, mAidCache, this); mEnabledNfcFServices = new EnabledNfcFServices( context, mNfcFServicesCache, mT3tIdentifiersCache, this); mServiceCache.initialize(); mNfcFServicesCache.initialize(); mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); } public INfcCardEmulation getNfcCardEmulationInterface() { return mCardEmulationInterface; } public INfcFCardEmulation getNfcFCardEmulationInterface() { return mNfcFCardEmulationInterface; } public void onHostCardEmulationActivated(int technology) { if (mPowerManager != null) { mPowerManager.userActivity(SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); } if (technology == NFC_HCE_APDU) { mHostEmulationManager.onHostEmulationActivated(); mPreferredServices.onHostEmulationActivated(); } else if (technology == NFC_HCE_NFCF) { mHostNfcFEmulationManager.onHostEmulationActivated(); mNfcFServicesCache.onHostEmulationActivated(); mEnabledNfcFServices.onHostEmulationActivated(); } } public void onHostCardEmulationData(int technology, byte[] data) { if (mPowerManager != null) { mPowerManager.userActivity(SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); } if (technology == NFC_HCE_APDU) { mHostEmulationManager.onHostEmulationData(data); } else if (technology == NFC_HCE_NFCF) { mHostNfcFEmulationManager.onHostEmulationData(data); } } public void onHostCardEmulationDeactivated(int technology) { if (technology == NFC_HCE_APDU) { mHostEmulationManager.onHostEmulationDeactivated(); mPreferredServices.onHostEmulationDeactivated(); } else if (technology == NFC_HCE_NFCF) { mHostNfcFEmulationManager.onHostEmulationDeactivated(); mNfcFServicesCache.onHostEmulationDeactivated(); mEnabledNfcFServices.onHostEmulationDeactivated(); } } public void onOffHostAidSelected() { mHostEmulationManager.onOffHostAidSelected(); } public void onUserSwitched(int userId) { // for HCE mServiceCache.invalidateCache(userId); mPreferredServices.onUserSwitched(userId); // for HCE-F mHostNfcFEmulationManager.onUserSwitched(); mT3tIdentifiersCache.onUserSwitched(); mEnabledNfcFServices.onUserSwitched(userId); mNfcFServicesCache.onUserSwitched(); mNfcFServicesCache.invalidateCache(userId); } public void onNfcEnabled() { // for HCE mAidCache.onNfcEnabled(); // for HCE-F mT3tIdentifiersCache.onNfcEnabled(); } public void onNfcDisabled() { // for HCE mAidCache.onNfcDisabled(); // for HCE-F mHostNfcFEmulationManager.onNfcDisabled(); mNfcFServicesCache.onNfcDisabled(); mT3tIdentifiersCache.onNfcDisabled(); mEnabledNfcFServices.onNfcDisabled(); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mServiceCache.dump(fd, pw, args); mNfcFServicesCache.dump(fd, pw ,args); mPreferredServices.dump(fd, pw, args); mEnabledNfcFServices.dump(fd, pw, args); mAidCache.dump(fd, pw, args); mT3tIdentifiersCache.dump(fd, pw, args); mHostEmulationManager.dump(fd, pw, args); mHostNfcFEmulationManager.dump(fd, pw, args); } @Override public void onServicesUpdated(int userId, List services) { // Verify defaults are still sane verifyDefaults(userId, services); // Update the AID cache mAidCache.onServicesUpdated(userId, services); // Update the preferred services list mPreferredServices.onServicesUpdated(); } @Override public void onNfcFServicesUpdated(int userId, List services) { // Update the T3T identifier cache mT3tIdentifiersCache.onServicesUpdated(userId, services); // Update the enabled services list mEnabledNfcFServices.onServicesUpdated(); } void verifyDefaults(int userId, List services) { ComponentName defaultPaymentService = getDefaultServiceForCategory(userId, CardEmulation.CATEGORY_PAYMENT, false); if (DBG) Log.d(TAG, "Current default: " + defaultPaymentService); if (defaultPaymentService == null) { // A payment service may have been removed, leaving only one; // in that case, automatically set that app as default. int numPaymentServices = 0; ComponentName lastFoundPaymentService = null; for (ApduServiceInfo service : services) { if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT)) { numPaymentServices++; lastFoundPaymentService = service.getComponent(); } } if (numPaymentServices > 1) { // More than one service left, leave default unset if (DBG) Log.d(TAG, "No default set, more than one service left."); } else if (numPaymentServices == 1) { // Make single found payment service the default if (DBG) Log.d(TAG, "No default set, making single service default."); setDefaultServiceForCategoryChecked(userId, lastFoundPaymentService, CardEmulation.CATEGORY_PAYMENT); } else { // No payment services left, leave default at null if (DBG) Log.d(TAG, "No default set, last payment service removed."); } } } ComponentName getDefaultServiceForCategory(int userId, String category, boolean validateInstalled) { if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) { Log.e(TAG, "Not allowing defaults for category " + category); return null; } // Load current payment default from settings String name = Settings.Secure.getStringForUser( mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, userId); if (name != null) { ComponentName service = ComponentName.unflattenFromString(name); if (!validateInstalled || service == null) { return service; } else { return mServiceCache.hasService(userId, service) ? service : null; } } else { return null; } } boolean setDefaultServiceForCategoryChecked(int userId, ComponentName service, String category) { if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) { Log.e(TAG, "Not allowing defaults for category " + category); return false; } // TODO Not really nice to be writing to Settings.Secure here... // ideally we overlay our local changes over whatever is in // Settings.Secure if (service == null || mServiceCache.hasService(userId, service)) { Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, service != null ? service.flattenToString() : null, userId); } else { Log.e(TAG, "Could not find default service to make default: " + service); } return true; } boolean isServiceRegistered(int userId, ComponentName service) { boolean serviceFound = mServiceCache.hasService(userId, service); if (!serviceFound) { // If we don't know about this service yet, it may have just been enabled // using PackageManager.setComponentEnabledSetting(). The PackageManager // broadcasts are delayed by 10 seconds in that scenario, which causes // calls to our APIs referencing that service to fail. // Hence, update the cache in case we don't know about the service. if (DBG) Log.d(TAG, "Didn't find passed in service, invalidating cache."); mServiceCache.invalidateCache(userId); } return mServiceCache.hasService(userId, service); } boolean isNfcFServiceInstalled(int userId, ComponentName service) { boolean serviceFound = mNfcFServicesCache.hasService(userId, service); if (!serviceFound) { // If we don't know about this service yet, it may have just been enabled // using PackageManager.setComponentEnabledSetting(). The PackageManager // broadcasts are delayed by 10 seconds in that scenario, which causes // calls to our APIs referencing that service to fail. // Hence, update the cache in case we don't know about the service. if (DBG) Log.d(TAG, "Didn't find passed in service, invalidating cache."); mNfcFServicesCache.invalidateCache(userId); } return mNfcFServicesCache.hasService(userId, service); } /** * Returns whether a service in this package is preferred, * either because it's the default payment app or it's running * in the foreground. */ public boolean packageHasPreferredService(String packageName) { return mPreferredServices.packageHasPreferredService(packageName); } /** * This class implements the application-facing APIs and are called * from binder. All calls must be permission-checked. */ final class CardEmulationInterface extends INfcCardEmulation.Stub { @Override public boolean isDefaultServiceForCategory(int userId, ComponentName service, String category) { NfcPermissions.enforceUserPermissions(mContext); NfcPermissions.validateUserId(userId); if (!isServiceRegistered(userId, service)) { return false; } ComponentName defaultService = getDefaultServiceForCategory(userId, category, true); return (defaultService != null && defaultService.equals(service)); } @Override public boolean isDefaultServiceForAid(int userId, ComponentName service, String aid) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceUserPermissions(mContext); if (!isServiceRegistered(userId, service)) { return false; } return mAidCache.isDefaultServiceForAid(userId, service, aid); } @Override public boolean setDefaultServiceForCategory(int userId, ComponentName service, String category) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceAdminPermissions(mContext); if (!isServiceRegistered(userId, service)) { return false; } return setDefaultServiceForCategoryChecked(userId, service, category); } @Override public boolean setDefaultForNextTap(int userId, ComponentName service) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceAdminPermissions(mContext); if (service != null && !isServiceRegistered(userId, service)) { return false; } return mPreferredServices.setDefaultForNextTap(service); } @Override public boolean registerAidGroupForService(int userId, ComponentName service, AidGroup aidGroup) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceUserPermissions(mContext); if (!isServiceRegistered(userId, service)) { return false; } return mServiceCache.registerAidGroupForService(userId, Binder.getCallingUid(), service, aidGroup); } @Override public AidGroup getAidGroupForService(int userId, ComponentName service, String category) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceUserPermissions(mContext); if (!isServiceRegistered(userId, service)) { return null; } return mServiceCache.getAidGroupForService(userId, Binder.getCallingUid(), service, category); } @Override public boolean removeAidGroupForService(int userId, ComponentName service, String category) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceUserPermissions(mContext); if (!isServiceRegistered(userId, service)) { return false; } return mServiceCache.removeAidGroupForService(userId, Binder.getCallingUid(), service, category); } @Override public List getServices(int userId, String category) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceAdminPermissions(mContext); return mServiceCache.getServicesForCategory(userId, category); } @Override public boolean setPreferredService(ComponentName service) throws RemoteException { NfcPermissions.enforceUserPermissions(mContext); if (!isServiceRegistered(UserHandle.getCallingUserId(), service)) { Log.e(TAG, "setPreferredService: unknown component."); return false; } return mPreferredServices.registerPreferredForegroundService(service, Binder.getCallingUid()); } @Override public boolean unsetPreferredService() throws RemoteException { NfcPermissions.enforceUserPermissions(mContext); return mPreferredServices.unregisteredPreferredForegroundService( Binder.getCallingUid()); } @Override public boolean supportsAidPrefixRegistration() throws RemoteException { return mAidCache.supportsAidPrefixRegistration(); } } /** * This class implements the application-facing APIs and are called * from binder. All calls must be permission-checked. */ final class NfcFCardEmulationInterface extends INfcFCardEmulation.Stub { @Override public String getSystemCodeForService(int userId, ComponentName service) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceUserPermissions(mContext); if (!isNfcFServiceInstalled(userId, service)) { return null; } return mNfcFServicesCache.getSystemCodeForService( userId, Binder.getCallingUid(), service); } @Override public boolean registerSystemCodeForService(int userId, ComponentName service, String systemCode) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceUserPermissions(mContext); if (!isNfcFServiceInstalled(userId, service)) { return false; } return mNfcFServicesCache.registerSystemCodeForService( userId, Binder.getCallingUid(), service, systemCode); } @Override public boolean removeSystemCodeForService(int userId, ComponentName service) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceUserPermissions(mContext); if (!isNfcFServiceInstalled(userId, service)) { return false; } return mNfcFServicesCache.removeSystemCodeForService( userId, Binder.getCallingUid(), service); } @Override public String getNfcid2ForService(int userId, ComponentName service) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceUserPermissions(mContext); if (!isNfcFServiceInstalled(userId, service)) { return null; } return mNfcFServicesCache.getNfcid2ForService( userId, Binder.getCallingUid(), service); } @Override public boolean setNfcid2ForService(int userId, ComponentName service, String nfcid2) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceUserPermissions(mContext); if (!isNfcFServiceInstalled(userId, service)) { return false; } return mNfcFServicesCache.setNfcid2ForService( userId, Binder.getCallingUid(), service, nfcid2); } @Override public boolean enableNfcFForegroundService(ComponentName service) throws RemoteException { NfcPermissions.enforceUserPermissions(mContext); if (isNfcFServiceInstalled(UserHandle.getCallingUserId(), service)) { return mEnabledNfcFServices.registerEnabledForegroundService(service, Binder.getCallingUid()); } return false; } @Override public boolean disableNfcFForegroundService() throws RemoteException { NfcPermissions.enforceUserPermissions(mContext); return mEnabledNfcFServices.unregisteredEnabledForegroundService( Binder.getCallingUid()); } @Override public List getNfcFServices(int userId) throws RemoteException { NfcPermissions.validateUserId(userId); NfcPermissions.enforceUserPermissions(mContext); return mNfcFServicesCache.getServices(userId); } @Override public int getMaxNumOfRegisterableSystemCodes() throws RemoteException { NfcPermissions.enforceUserPermissions(mContext); return NfcService.getInstance().getLfT3tMax(); } } @Override public void onPreferredPaymentServiceChanged(ComponentName service) { mAidCache.onPreferredPaymentServiceChanged(service); mHostEmulationManager.onPreferredPaymentServiceChanged(service); } @Override public void onPreferredForegroundServiceChanged(ComponentName service) { mAidCache.onPreferredForegroundServiceChanged(service); mHostEmulationManager.onPreferredForegroundServiceChanged(service); } @Override public void onEnabledForegroundNfcFServiceChanged(ComponentName service) { mT3tIdentifiersCache.onEnabledForegroundNfcFServiceChanged(service); mHostNfcFEmulationManager.onEnabledForegroundNfcFServiceChanged(service); } }