blob: 4d0919d150c970ed397f5633b72dc47b4c9935e7 [file] [log] [blame]
/*
* 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 android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.nfc.Constants;
import android.nfc.INfcCardEmulation;
import android.nfc.INfcFCardEmulation;
import android.nfc.NfcAdapter;
import android.nfc.NfcManager;
import android.nfc.cardemulation.AidGroup;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
import android.nfc.cardemulation.NfcFServiceInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.sysprop.NfcProperties;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.nfc.ForegroundUtils;
import com.android.nfc.NfcPermissions;
import com.android.nfc.NfcService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import com.android.nfc.R;
/**
* 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 = NfcProperties.debug_enabled().orElse(false);
static final int NFC_HCE_APDU = 0x01;
static final int NFC_HCE_NFCF = 0x04;
/** Minimum AID length as per ISO7816 */
static final int MINIMUM_AID_LENGTH = 5;
/** Length of Select APDU header including length byte */
static final int SELECT_APDU_HDR_LENGTH = 5;
/** Length of the NDEF Tag application AID */
static final int NDEF_AID_LENGTH = 7;
/** AID of the NDEF Tag application Mapping Version 1.0 */
static final byte[] NDEF_AID_V1 =
new byte[] {(byte) 0xd2, 0x76, 0x00, 0x00, (byte) 0x85, 0x01, 0x00};
/** AID of the NDEF Tag application Mapping Version 2.0 */
static final byte[] NDEF_AID_V2 =
new byte[] {(byte) 0xd2, 0x76, 0x00, 0x00, (byte) 0x85, 0x01, 0x01};
/** Select APDU header */
static final byte[] SELECT_AID_HDR = new byte[] {0x00, (byte) 0xa4, 0x04, 0x00};
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;
boolean mNotSkipAid;
final ForegroundUtils mForegroundUtils;
private int mForegroundUid;
RoutingOptionManager mRoutingOptionManager;
final byte[] mOffHostRouteUicc;
final byte[] mOffHostRouteEse;
public CardEmulationManager(Context context) {
mContext = context;
mCardEmulationInterface = new CardEmulationInterface();
mNfcFCardEmulationInterface = new NfcFCardEmulationInterface();
mForegroundUtils = ForegroundUtils.getInstance(context.getSystemService(ActivityManager.class));
mAidCache = new RegisteredAidCache(context);
mT3tIdentifiersCache = new RegisteredT3tIdentifiersCache(context);
mHostEmulationManager =
new HostEmulationManager(context, Looper.getMainLooper(), 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 = context.getSystemService(PowerManager.class);
mRoutingOptionManager = RoutingOptionManager.getInstance();
mOffHostRouteEse = mRoutingOptionManager.getOffHostRouteEse();
mOffHostRouteUicc = mRoutingOptionManager.getOffHostRouteUicc();
mForegroundUid = Process.INVALID_UID;
}
public INfcCardEmulation getNfcCardEmulationInterface() {
return mCardEmulationInterface;
}
public INfcFCardEmulation getNfcFCardEmulationInterface() {
return mNfcFCardEmulationInterface;
}
public void onPollingLoopDetected(Bundle pollingFrame) {
mHostEmulationManager.onPollingLoopDetected(pollingFrame);
}
public void onHostCardEmulationActivated(int technology) {
if (mPowerManager != null) {
// Use USER_ACTIVITY_FLAG_INDIRECT to applying power hints without resets
// the screen timeout
mPowerManager.userActivity(SystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH,
PowerManager.USER_ACTIVITY_FLAG_INDIRECT);
}
if (technology == NFC_HCE_APDU) {
mHostEmulationManager.onHostEmulationActivated();
mPreferredServices.onHostEmulationActivated();
mNotSkipAid = false;
} else if (technology == NFC_HCE_NFCF) {
mHostNfcFEmulationManager.onHostEmulationActivated();
mNfcFServicesCache.onHostEmulationActivated();
mEnabledNfcFServices.onHostEmulationActivated();
}
}
public void onHostCardEmulationData(int technology, byte[] data) {
if (technology == NFC_HCE_APDU) {
mHostEmulationManager.onHostEmulationData(data);
} else if (technology == NFC_HCE_NFCF) {
mHostNfcFEmulationManager.onHostEmulationData(data);
}
// Don't trigger userActivity if it's selecting NDEF AID
if (mPowerManager != null && !(technology == NFC_HCE_APDU && isSkipAid(data))) {
// Caution!! USER_ACTIVITY_EVENT_TOUCH resets the screen timeout
mPowerManager.userActivity(SystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
}
}
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.onUserSwitched();
mPreferredServices.onUserSwitched(userId);
// for HCE-F
mHostNfcFEmulationManager.onUserSwitched();
mT3tIdentifiersCache.onUserSwitched();
mEnabledNfcFServices.onUserSwitched(userId);
mNfcFServicesCache.onUserSwitched();
}
public void onManagedProfileChanged() {
// for HCE
mServiceCache.onManagedProfileChanged();
// for HCE-F
mNfcFServicesCache.onManagedProfileChanged();
}
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 onSecureNfcToggled() {
mAidCache.onSecureNfcToggled();
mT3tIdentifiersCache.onSecureNfcToggled();
}
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);
}
/**
* Dump debugging information as a CardEmulationManagerProto
*
* Note:
* See proto definition in frameworks/base/core/proto/android/nfc/card_emulation.proto
* When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
* {@link ProtoOutputStream#end(long)} after.
* Never reuse a proto field number. When removing a field, mark it as reserved.
*/
public void dumpDebug(ProtoOutputStream proto) {
long token = proto.start(CardEmulationManagerProto.REGISTERED_SERVICES_CACHE);
mServiceCache.dumpDebug(proto);
proto.end(token);
token = proto.start(CardEmulationManagerProto.REGISTERED_NFC_F_SERVICES_CACHE);
mNfcFServicesCache.dumpDebug(proto);
proto.end(token);
token = proto.start(CardEmulationManagerProto.PREFERRED_SERVICES);
mPreferredServices.dumpDebug(proto);
proto.end(token);
token = proto.start(CardEmulationManagerProto.ENABLED_NFC_F_SERVICES);
mEnabledNfcFServices.dumpDebug(proto);
proto.end(token);
token = proto.start(CardEmulationManagerProto.AID_CACHE);
mAidCache.dumpDebug(proto);
proto.end(token);
token = proto.start(CardEmulationManagerProto.T3T_IDENTIFIERS_CACHE);
mT3tIdentifiersCache.dumpDebug(proto);
proto.end(token);
token = proto.start(CardEmulationManagerProto.HOST_EMULATION_MANAGER);
mHostEmulationManager.dumpDebug(proto);
proto.end(token);
token = proto.start(CardEmulationManagerProto.HOST_NFC_F_EMULATION_MANAGER);
mHostNfcFEmulationManager.dumpDebug(proto);
proto.end(token);
}
@Override
public void onServicesUpdated(int userId, List<ApduServiceInfo> services,
boolean validateInstalled) {
// Verify defaults are still the same
verifyDefaults(userId, services, validateInstalled);
// Update the AID cache
mAidCache.onServicesUpdated(userId, services);
// Update the preferred services list
mPreferredServices.onServicesUpdated();
NfcService.getInstance().onPreferredPaymentChanged(NfcAdapter.PREFERRED_PAYMENT_UPDATED);
}
@Override
public void onNfcFServicesUpdated(int userId, List<NfcFServiceInfo> services) {
// Update the T3T identifier cache
mT3tIdentifiersCache.onServicesUpdated(userId, services);
// Update the enabled services list
mEnabledNfcFServices.onServicesUpdated();
}
void verifyDefaults(int userId, List<ApduServiceInfo> services, boolean validateInstalled) {
UserManager um = mContext.createContextAsUser(
UserHandle.of(userId), /*flags=*/0).getSystemService(UserManager.class);
List<UserHandle> luh = um.getEnabledProfiles();
ComponentName defaultPaymentService = null;
int numDefaultPaymentServices = 0;
int userIdDefaultPaymentService = userId;
for (UserHandle uh : luh) {
ComponentName paymentService = getDefaultServiceForCategory(uh.getIdentifier(),
CardEmulation.CATEGORY_PAYMENT,
validateInstalled && (uh.getIdentifier() == userId));
if (DBG) Log.d(TAG, "default: " + paymentService + " for user:" + uh);
if (paymentService != null) {
numDefaultPaymentServices++;
defaultPaymentService = paymentService;
userIdDefaultPaymentService = uh.getIdentifier();
}
}
if (numDefaultPaymentServices > 1) {
Log.e(TAG, "Current default is not aligned across multiple users");
// leave default unset
for (UserHandle uh : luh) {
setDefaultServiceForCategoryChecked(uh.getIdentifier(), null,
CardEmulation.CATEGORY_PAYMENT);
}
} else {
if (DBG) {
Log.d(TAG, "Current default: " + defaultPaymentService + " for user:"
+ userIdDefaultPaymentService);
}
}
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;
PackageManager pm;
try {
pm = mContext.createPackageContextAsUser("android", /*flags=*/0,
UserHandle.of(userId)).getPackageManager();
} catch (NameNotFoundException e) {
Log.e(TAG, "Could not create user package context");
return;
}
for (ApduServiceInfo service : services) {
if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT)
&& wasServicePreInstalled(pm, service.getComponent())) {
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.");
setDefaultServiceForCategoryChecked(userId, null, CardEmulation.CATEGORY_PAYMENT);
} 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.");
setDefaultServiceForCategoryChecked(userId, null, CardEmulation.CATEGORY_PAYMENT);
}
}
}
boolean wasServicePreInstalled(PackageManager packageManager, ComponentName service) {
try {
ApplicationInfo ai = packageManager
.getApplicationInfo(service.getPackageName(), /*flags=*/0);
if ((ApplicationInfo.FLAG_SYSTEM & ai.flags) != 0) {
if (DBG) Log.d(TAG, "Service was pre-installed on the device");
return true;
}
} catch (NameNotFoundException e) {
Log.e(TAG, "Service is not currently installed on the device.");
return false;
}
if (DBG) Log.d(TAG, "Service was not pre-installed on the device");
return false;
}
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.getString(
mContext.createContextAsUser(UserHandle.of(userId), 0).getContentResolver(),
Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT);
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.putString(mContext
.createContextAsUser(UserHandle.of(userId), 0).getContentResolver(),
Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT,
service != null ? service.flattenToString() : null);
} 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, true);
}
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 true if it's not selecting NDEF AIDs
* It's used to skip userActivity if it only selects NDEF AIDs
*/
boolean isSkipAid(byte[] data) {
if (mNotSkipAid || data == null
|| data.length < SELECT_APDU_HDR_LENGTH + MINIMUM_AID_LENGTH
|| !Arrays.equals(SELECT_AID_HDR, 0, SELECT_AID_HDR.length,
data, 0, SELECT_AID_HDR.length)) {
return false;
}
int aidLength = Byte.toUnsignedInt(data[SELECT_APDU_HDR_LENGTH - 1]);
if (data.length >= SELECT_APDU_HDR_LENGTH + NDEF_AID_LENGTH
&& aidLength == NDEF_AID_LENGTH) {
if (Arrays.equals(data, SELECT_APDU_HDR_LENGTH,
SELECT_APDU_HDR_LENGTH + NDEF_AID_LENGTH,
NDEF_AID_V1, 0, NDEF_AID_LENGTH)) {
if (DBG) Log.d(TAG, "Skip for NDEF_V1");
return true;
} else if (Arrays.equals(data, SELECT_APDU_HDR_LENGTH,
SELECT_APDU_HDR_LENGTH + NDEF_AID_LENGTH,
NDEF_AID_V2, 0, NDEF_AID_LENGTH)) {
if (DBG) Log.d(TAG, "Skip for NDEF_V2");
return true;
}
}
// The data payload is not selecting the skip AID.
mNotSkipAid = true;
return false;
}
/**
* 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.validateProfileId(mContext, userId);
NfcPermissions.enforceAdminPermissions(mContext);
if (service != null && !isServiceRegistered(userId, service)) {
return false;
}
return mPreferredServices.setDefaultForNextTap(userId, service);
}
@Override
public boolean setServiceObserveModeDefault(int userId,
ComponentName service, boolean enable) {
NfcPermissions.validateUserId(userId);
if (!isServiceRegistered(userId, service)) {
return false;
}
return mServiceCache.setServiceObserveModeDefault(userId, Binder.getCallingUid(),
service, enable);
}
@Override
public boolean registerAidGroupForService(int userId,
ComponentName service, AidGroup aidGroup) throws RemoteException {
NfcPermissions.validateUserId(userId);
NfcPermissions.enforceUserPermissions(mContext);
if (!isServiceRegistered(userId, service)) {
return false;
}
if (!mServiceCache.registerAidGroupForService(userId, Binder.getCallingUid(), service,
aidGroup)) {
return false;
}
NfcService.getInstance().onPreferredPaymentChanged(
NfcAdapter.PREFERRED_PAYMENT_UPDATED);
return true;
}
@Override
public boolean setOffHostForService(int userId, ComponentName service, String offHostSE) {
NfcPermissions.validateUserId(userId);
NfcPermissions.enforceUserPermissions(mContext);
if (!isServiceRegistered(userId, service)) {
return false;
}
if (!mServiceCache.setOffHostSecureElement(userId, Binder.getCallingUid(), service,
offHostSE)) {
return false;
}
NfcService.getInstance().onPreferredPaymentChanged(
NfcAdapter.PREFERRED_PAYMENT_UPDATED);
return true;
}
@Override
public boolean unsetOffHostForService(int userId, ComponentName service) {
NfcPermissions.validateUserId(userId);
NfcPermissions.enforceUserPermissions(mContext);
if (!isServiceRegistered(userId, service)) {
return false;
}
if (!mServiceCache.resetOffHostSecureElement(userId, Binder.getCallingUid(), service)) {
return false;
}
NfcService.getInstance().onPreferredPaymentChanged(
NfcAdapter.PREFERRED_PAYMENT_UPDATED);
return true;
}
@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;
}
if (!mServiceCache.removeAidGroupForService(userId, Binder.getCallingUid(), service,
category)) {
return false;
}
NfcService.getInstance().onPreferredPaymentChanged(
NfcAdapter.PREFERRED_PAYMENT_UPDATED);
return true;
}
@Override
public List<ApduServiceInfo> getServices(int userId, String category)
throws RemoteException {
NfcPermissions.validateProfileId(mContext, userId);
NfcPermissions.enforceAdminPermissions(mContext);
return mServiceCache.getServicesForCategory(userId, category);
}
@Override
public boolean setPreferredService(ComponentName service)
throws RemoteException {
NfcPermissions.enforceUserPermissions(mContext);
if (!isServiceRegistered( UserHandle.getUserHandleForUid(
Binder.getCallingUid()).getIdentifier(), 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();
}
@Override
public ApduServiceInfo getPreferredPaymentService(int userId) throws RemoteException {
NfcPermissions.validateUserId(userId);
NfcPermissions.enforceUserPermissions(mContext);
NfcPermissions.enforcePreferredPaymentInfoPermissions(mContext);
return mServiceCache.getService(userId, mAidCache.getPreferredService());
}
@Override
public boolean setServiceEnabledForCategoryOther(int userId,
ComponentName app, boolean status) throws RemoteException {
if (!mContext.getResources().getBoolean(R.bool.enable_service_for_category_other))
return false;
NfcPermissions.enforceUserPermissions(mContext);
return mServiceCache.registerOtherForService(userId, app, status);
}
@Override
public boolean isDefaultPaymentRegistered() throws RemoteException {
String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(),
Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT);
return defaultComponent != null ? true : false;
}
@Override
public boolean overrideRoutingTable(int userHandle, String protocol, String technology) {
Log.d(TAG, "overrideRoutingTable. userHandle " + userHandle + ", protocol " + protocol +
", technology " + technology);
int callingUid = Binder.getCallingUid();
if (!mForegroundUtils.registerUidToBackgroundCallback(mForegroundCallback, callingUid)) {
Log.e(TAG, "overrideRoutingTable: Caller is not in foreground.");
return false;
}
mForegroundUid = callingUid;
int protocolRoute = getRouteForSecureElement(protocol);
int technologyRoute = getRouteForSecureElement(technology);
if (DBG) Log.d(TAG, "protocolRoute " + protocolRoute + ", technologyRoute " + technologyRoute);
// mRoutingOptionManager.overrideDefaultRoute(protocolRoute);
mRoutingOptionManager.overrideDefaultIsoDepRoute(protocolRoute);
mRoutingOptionManager.overrideDefaultOffHostRoute(technologyRoute);
mAidCache.onRoutingOverridedOrRecovered();
// NfcService.getInstance().commitRouting();
return true;
}
@Override
public boolean recoverRoutingTable(int userHandle) {
Log.d(TAG, "recoverRoutingTable. userHandle " + userHandle);
if (!mForegroundUtils.isInForeground(Binder.getCallingUid())) {
if (DBG) Log.d(TAG, "recoverRoutingTable : not in foreground.");
return false;
}
mForegroundUid = Process.INVALID_UID;
mRoutingOptionManager.recoverOverridedRoutingTable();
mAidCache.onRoutingOverridedOrRecovered();
// NfcService.getInstance().commitRouting();
return true;
}
}
final ForegroundUtils.Callback mForegroundCallback = new ForegroundCallbackImpl();
class ForegroundCallbackImpl implements ForegroundUtils.Callback {
@Override
public void onUidToBackground(int uid) {
synchronized (CardEmulationManager.this) {
if (mForegroundUid == uid) {
if (DBG) Log.d(TAG, "Uid " + uid + " switch to background.");
mForegroundUid = Process.INVALID_UID;
mRoutingOptionManager.recoverOverridedRoutingTable();
}
}
}
}
private int getRouteForSecureElement(String se) {
String route = se;
if (route == null) {
return -1;
}
if (route.equals("DH")) {
return 0;
}
if (route.length() == 3) {
route = route + '1';
}
try {
if (route.startsWith("eSE") && mOffHostRouteEse != null) {
int index = Integer.parseInt(route.substring(3));
if (mOffHostRouteEse.length >= index && index > 0) {
return mOffHostRouteEse[index - 1] & 0xFF;
}
} else if (route.startsWith("SIM") && mOffHostRouteUicc != null) {
int index = Integer.parseInt(route.substring(3));
if (mOffHostRouteUicc.length >= index && index > 0) {
return mOffHostRouteUicc[index - 1] & 0xFF;
}
}
if (mOffHostRouteEse == null && mOffHostRouteUicc == null)
return -1;
} catch (NumberFormatException ignored) { }
return 0;
}
/**
* 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.getUserHandleForUid(
Binder.getCallingUid()).getIdentifier(), 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<NfcFServiceInfo> getNfcFServices(int userId)
throws RemoteException {
NfcPermissions.validateProfileId(mContext, 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(int userId, ComponentName service) {
mAidCache.onPreferredPaymentServiceChanged(userId, service);
mHostEmulationManager.onPreferredPaymentServiceChanged(userId, service);
NfcService.getInstance().onPreferredPaymentChanged(
NfcAdapter.PREFERRED_PAYMENT_CHANGED);
}
@Override
public void onPreferredForegroundServiceChanged(int userId, ComponentName service) {
mAidCache.onPreferredForegroundServiceChanged(userId, service);
mHostEmulationManager.onPreferredForegroundServiceChanged(userId, service);
NfcService.getInstance().onPreferredPaymentChanged(
NfcAdapter.PREFERRED_PAYMENT_CHANGED);
long token = Binder.clearCallingIdentity();
try {
if (!android.nfc.Flags.nfcObserveMode()) {
return;
}
} finally {
Binder.restoreCallingIdentity(token);
}
ComponentName paymentService = getDefaultServiceForCategory(userId,
CardEmulation.CATEGORY_PAYMENT, false);
NfcManager manager = mContext.getSystemService(NfcManager.class);
NfcAdapter adapter = manager.getDefaultAdapter();
if (mServiceCache.doesServiceDefaultToObserveMode(userId,
service != null ? service : paymentService)) {
adapter.disallowTransaction();
} else {
adapter.allowTransaction();
}
}
@Override
public void onEnabledForegroundNfcFServiceChanged(int userId, ComponentName service) {
mT3tIdentifiersCache.onEnabledForegroundNfcFServiceChanged(userId, service);
mHostNfcFEmulationManager.onEnabledForegroundNfcFServiceChanged(userId, service);
}
public String getRegisteredAidCategory(String aid) {
RegisteredAidCache.AidResolveInfo resolvedInfo = mAidCache.resolveAid(aid);
if (resolvedInfo != null) {
return resolvedInfo.category;
}
return "";
}
public boolean isRequiresScreenOnServiceExist() {
return mAidCache.isRequiresScreenOnServiceExist();
}
}