/* * Copyright (C) 2013 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 static com.android.nfc.module.flags.Flags.nfcHceLatencyEvents; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TargetApi; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.nfc.ComponentNameAndUser; import android.nfc.INfcOemExtensionCallback; import android.nfc.NfcAdapter; import android.nfc.OemLogItems; import android.nfc.cardemulation.ApduServiceInfo; import android.nfc.cardemulation.CardEmulation; import android.nfc.cardemulation.HostApduService; import android.nfc.cardemulation.PollingFrame; import android.nfc.cardemulation.Utils; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.sysprop.NfcProperties; import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.proto.ProtoOutputStream; import androidx.annotation.VisibleForTesting; import com.android.nfc.DeviceConfigFacade; import com.android.nfc.ForegroundUtils; import com.android.nfc.NfcInjector; import com.android.nfc.NfcService; import com.android.nfc.NfcStatsLog; import com.android.nfc.PerfettoTrigger; import com.android.nfc.cardemulation.RegisteredAidCache.AidResolveInfo; import com.android.nfc.cardemulation.util.StatsdUtils; import com.android.nfc.flags.Flags; import com.android.nfc.proto.NfcEventProto; import java.io.FileDescriptor; import java.io.PrintWriter; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HexFormat; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.regex.Pattern; public class HostEmulationManager { static final String TAG = "HostEmulationManager"; static final boolean DBG = NfcProperties.debug_enabled().orElse(true); static final int STATE_IDLE = 0; static final int STATE_W4_SELECT = 1; static final int STATE_W4_SERVICE = 2; static final int STATE_W4_DEACTIVATE = 3; static final int STATE_XFER = 4; static final int STATE_POLLING_LOOP = 5; /** 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; static final byte INSTR_SELECT = (byte)0xA4; static final String ANDROID_HCE_AID = "A000000476416E64726F6964484345"; static final String NDEF_V1_AID = "D2760000850100"; static final String NDEF_V2_AID = "D2760000850101"; static final byte[] ANDROID_HCE_RESPONSE = {0x14, (byte)0x81, 0x00, 0x00, (byte)0x90, 0x00}; static final byte[] AID_NOT_FOUND = {0x6A, (byte)0x82}; static final byte[] UNKNOWN_ERROR = {0x6F, 0x00}; static final int CE_HCE_PAYMENT = NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__HCE_PAYMENT; static final int CE_HCE_OTHER = NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__HCE_OTHER; static final String DATA_KEY = "data"; static final int FIELD_OFF_IDLE_DELAY_MS = 2000; static final int RE_ENABLE_OBSERVE_MODE_DELAY_MS = 2000; static final int UNBIND_SERVICES_DELAY_MS = 10_000; static final String EVENT_HCE_ACTIVATED = "hce_active"; static final String EVENT_HCE_BIND_PAYMENT_SERVICE = "hce_bind_payment_service"; static final String EVENT_HCE_BIND_SERVICE = "hce_bind_service"; static final String EVENT_HCE_COMMAND_APDU = "hce_command_apdu"; static final String EVENT_POLLING_FRAMES = "hce_polling_frames"; static final String TRIGGER_NAME_SLOW_TAP = "android.nfc.slow-tap"; final Context mContext; final RegisteredAidCache mAidCache; final Messenger mMessenger = new Messenger (new MessageHandler()); final KeyguardManager mKeyguard; final Object mLock; final PowerManager mPowerManager; private final Looper mLooper; final DeviceConfigFacade mDeviceConfig; @Nullable private final StatsdUtils mStatsdUtils; private final Random mCookieRandom = new Random(System.currentTimeMillis()); @ChangeId @EnabledSince(targetSdkVersion = 36 /*Build.VERSION_CODES.BAKLAVA*/) static final long DONT_IMMEDIATELY_UNBIND_SERVICES = 365533082L; INfcOemExtensionCallback mNfcOemExtensionCallback; long mFieldOnTime; // All variables below protected by mLock // Variables below are for a non-payment service, // that is typically only bound in the STATE_XFER state. Messenger mService; static class HostEmulationConnection { @UserIdInt int mUserId; ComponentName mComponentName; ServiceConnection mServiceConnection; Messenger mMessenger; HostEmulationConnection(@UserIdInt int userId, ComponentName componentName, ServiceConnection serviceConnection) { this(userId, componentName, serviceConnection, null); } HostEmulationConnection( @UserIdInt int userId, ComponentName componentName, ServiceConnection serviceConnection, Messenger messenger) { mUserId = userId; mComponentName = componentName; mServiceConnection = serviceConnection; mMessenger = messenger; } @Override public String toString() { return "{" + mComponentName + "(" + mUserId + "): " + mMessenger + ", " + mServiceConnection + "}"; } } Map mComponentNameToConnectionsMap = new HashMap<>(); boolean mServiceBound = false; ComponentName mServiceName = null; @UserIdInt int mServiceUserId; // The UserId of the non-payment service ArrayList mPendingPollingLoopFrames = null; ArrayList mUnprocessedPollingFrames = null; Map> mPollingFramesToSend = null; private Map>> mPollingLoopFilters; private Map>> mPollingLoopPatternFilters; AutoDisableObserveModeRunnable mAutoDisableObserveModeRunnable = null; // Variables below are for a payment service, // which is typically bound persistently to improve on // latency. Messenger mPaymentService; boolean mPaymentServiceBound = false; boolean mEnableObserveModeAfterTransaction = false; boolean mEnableObserveModeOnFieldOff = false; PollingFrame mFirmwareExitFrame = null; ComponentName mPaymentServiceName = null; @UserIdInt int mPaymentServiceUserId; // The userId of the payment service ComponentName mLastBoundPaymentServiceName; // mActiveService denotes the service interface // that is the current active one, until a new SELECT AID // comes in that may be resolved to a different service. // On deactivation, mActiveService stops being valid. Messenger mActiveService; ComponentName mActiveServiceName; @UserIdInt int mActiveServiceUserId; // The UserId of the current active one String mLastSelectedAid; int mState; byte[] mSelectApdu; Handler mHandler; enum PollingLoopState { EVALUATING_POLLING_LOOP, FILTER_MATCHED, DELIVERING_TO_PREFERRED }; PollingLoopState mPollingLoopState = PollingLoopState.EVALUATING_POLLING_LOOP; // Runnable to return to an IDLE_STATE and reset preferred service. This should be run after we // have left a field and gone a period of time without any HCE or polling frame data. Runnable mReturnToIdleStateRunnable = new Runnable() { @Override public void run() { synchronized (mLock) { Log.d(TAG, "Have been outside field, returning to idle state"); returnToIdleStateLocked(); } } }; Runnable mUnbindInactiveServicesRunnable = new Runnable() { @Override public void run() { synchronized (mLock) { if (isHostCardEmulationActivated()) { // Skip in active state rescheduleInactivityChecks(); } else { unbindInactiveServicesLocked(); } } } void unbindInactiveServicesLocked() { ComponentNameAndUser preferredNameAndUser = mAidCache.getPreferredService(); Map retainedConnections = new HashMap<>(); mComponentNameToConnectionsMap.keySet().forEach((key) -> { if (!preferredNameAndUser.equals(key)) { HostEmulationConnection connection = mComponentNameToConnectionsMap.get(key); if (connection.mMessenger != null) { try { mContext.unbindService(connection.mServiceConnection); } catch (IllegalArgumentException iae) { Log.wtf(TAG, "unbindInactiveServicesLocked: " + "Exception while unbinding " + key.getComponentName() + " service connection", iae); } } } else { retainedConnections.put(key, mComponentNameToConnectionsMap.get(key)); } }); mComponentNameToConnectionsMap = retainedConnections; } }; // Runnable to re-enable observe mode after a transaction. This should be delayed after // HCE is deactivated to ensure we don't receive another select AID. Runnable mEnableObserveModeAfterTransactionRunnable = new Runnable() { @Override public void run() { synchronized (mLock) { Log.d(TAG, "mEnableObserveModeAfterTransactionRunnable.run"); if (!mEnableObserveModeAfterTransaction && !mEnableObserveModeOnFieldOff) { return; } mEnableObserveModeAfterTransaction = false; mEnableObserveModeOnFieldOff = false; } NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); if (adapter == null) { Log.e(TAG, "mEnableObserveModeAfterTransactionRunnable.run: " + "adapter is null, returning"); return; } adapter.setObserveModeEnabled(true); } }; public HostEmulationManager(Context context, Looper looper, RegisteredAidCache aidCache, NfcInjector nfcInjector) { this(context, looper, aidCache, nfcInjector.getStatsdUtilsContext() != null ? new StatsdUtils(StatsdUtils.SE_NAME_HCE, nfcInjector.getStatsdUtilsContext()) : null, nfcInjector); } @VisibleForTesting HostEmulationManager(Context context, Looper looper, RegisteredAidCache aidCache, StatsdUtils statsdUtils, NfcInjector nfcInjector) { mContext = context; mLooper = looper; mHandler = new Handler(looper); mLock = new Object(); mAidCache = aidCache; mState = STATE_IDLE; mPollingLoopState = PollingLoopState.EVALUATING_POLLING_LOOP; mKeyguard = context.getSystemService(KeyguardManager.class); mPowerManager = context.getSystemService(PowerManager.class); mStatsdUtils = Flags.statsdCeEventsFlag() ? statsdUtils : null; mPollingLoopFilters = new HashMap>>(); mPollingLoopPatternFilters = new HashMap>>(); mDeviceConfig = nfcInjector.getDeviceConfigFacade(); if (isMultipleBindingSupported()) { mHandler.postDelayed(mUnbindInactiveServicesRunnable, UNBIND_SERVICES_DELAY_MS); } } public void setOemExtension(@Nullable INfcOemExtensionCallback nfcOemExtensionCallback) { mNfcOemExtensionCallback = nfcOemExtensionCallback; } public void onBootCompleted() { if (!mPaymentServiceBound) { ComponentNameAndUser preferredPaymentService = mAidCache.getPreferredPaymentService(); // getPreferredPaymentService returns a non-null object even if there is no role holder, // check for package name explicitly. ComponentName preferredPaymentServiceName = preferredPaymentService.getComponentName(); if (preferredPaymentServiceName != null) { Log.d(TAG, "onBootCompleted, payment service not bound, binding"); onPreferredPaymentServiceChanged(preferredPaymentService); } } } /** * Preferred payment service changed */ public void onPreferredPaymentServiceChanged(final ComponentNameAndUser service) { mHandler.post(() -> { synchronized (mLock) { if (!isHostCardEmulationActivated()) { Log.d(TAG, "onPreferredPaymentServiceChanged: resetting active service"); resetActiveService(); } if (service != null && service.getComponentName() != null) { bindPaymentServiceLocked(service.getUserId(), service.getComponentName()); } else { unbindPaymentServiceLocked(); } } }); } private Messenger getForegroundServiceOrDefault() { Pair pair = getForegroundServiceAndNameOrDefault(); if (pair == null) { return null; } return pair.first; } private Pair getForegroundServiceAndNameOrDefault() { ComponentNameAndUser preferredService = mAidCache.getPreferredService(); int preferredServiceUserId = preferredService.getUserId(); ComponentName preferredServiceName = preferredService.getComponentName(); if (preferredServiceName == null || preferredServiceUserId < 0) { return null; } return new Pair<>(bindServiceIfNeededLocked(preferredServiceUserId, preferredServiceName), preferredServiceName); } @TargetApi(35) public void updateForShouldDefaultToObserveMode(boolean enabled) { synchronized (mLock) { if (isHostCardEmulationActivated()) { mEnableObserveModeAfterTransaction = enabled; return; } if (mHandler.hasCallbacks(mEnableObserveModeAfterTransactionRunnable)) { if (enabled) { return; } else { mHandler.removeCallbacks(mEnableObserveModeAfterTransactionRunnable); } } mEnableObserveModeAfterTransaction = false; mEnableObserveModeOnFieldOff = false; } NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); adapter.setObserveModeEnabled(enabled); } @TargetApi(35) public void updatePollingLoopFilters(@UserIdInt int userId, List services) { HashMap> pollingLoopFilters = new HashMap>(); HashMap> pollingLoopPatternFilters = new HashMap>(); for (ApduServiceInfo serviceInfo : services) { for (String plf : serviceInfo.getPollingLoopFilters()) { List list = pollingLoopFilters.getOrDefault(plf, new ArrayList()); list.add(serviceInfo); pollingLoopFilters.putIfAbsent(plf, list); } for (Pattern plpf : serviceInfo.getPollingLoopPatternFilters()) { List list = pollingLoopPatternFilters.getOrDefault(plpf, new ArrayList()); list.add(serviceInfo); pollingLoopPatternFilters.putIfAbsent(plpf, list); } } mPollingLoopFilters.put(Integer.valueOf(userId), pollingLoopFilters); mPollingLoopPatternFilters.put(Integer.valueOf(userId), pollingLoopPatternFilters); } public void onObserveModeStateChange(boolean enabled) { synchronized(mLock) { if (!enabled && mAutoDisableObserveModeRunnable != null) { mHandler.removeCallbacks(mAutoDisableObserveModeRunnable); mAutoDisableObserveModeRunnable = null; } } } class AutoDisableObserveModeRunnable implements Runnable { Set mServicePackageNames; AutoDisableObserveModeRunnable(ComponentName componentName) { mServicePackageNames = new ArraySet<>(1); addServiceToList(componentName); } @Override public void run() { synchronized(mLock) { NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); if (!adapter.isObserveModeEnabled()) { return; } if (arePackagesInForeground()) { return; } Log.w(TAG, "AutoDisableObserveModeRunnable: Observe mode not disabled and " + "no application from the following " + "packages are in the foreground: " + String.join(", ", mServicePackageNames)); allowOneTransaction(); } } void addServiceToList(ComponentName service) { mServicePackageNames.add(service.getPackageName()); } boolean arePackagesInForeground() { ActivityManager am = mContext.getSystemService(ActivityManager.class); if (am == null) { return false; } ForegroundUtils foregroundUtils = ForegroundUtils.getInstance(am); if (foregroundUtils == null) { return false; } PackageManager packageManager = mContext.getPackageManager(); if (packageManager == null) { return false; } List uids = foregroundUtils.getForegroundUids(); if (uids != null && mServicePackageNames != null) { for (Integer uid : uids) { String[] packageNames = packageManager.getPackagesForUid(uid); if (packageNames != null) { for (String packageName : packageNames) { if (packageName != null) { for (String servicePackageName : mServicePackageNames) { if (Objects.equals(servicePackageName, packageName)) { return true; } } } } } } } return false; } } private void sendFrameToServiceLocked(Messenger service, ComponentName name, PollingFrame frame) { sendFramesToServiceLocked(service, name, Arrays.asList(frame)); } private void sendFramesToServiceLocked(Messenger service, ComponentName name, List frames) { if (service != null) { sendPollingFramesToServiceLocked(service, new ArrayList<>(frames)); } else { mUnprocessedPollingFrames = new ArrayList(); if (mPollingFramesToSend == null) { mPollingFramesToSend = new HashMap>(); } if (mPollingFramesToSend.containsKey(name)) { mPollingFramesToSend.get(name).addAll(frames); } else { mPollingFramesToSend.put(name, new ArrayList<>(frames)); } } if (Flags.autoDisableObserveMode()) { if (mAutoDisableObserveModeRunnable == null) { mAutoDisableObserveModeRunnable = new AutoDisableObserveModeRunnable(name); mHandler.postDelayed(mAutoDisableObserveModeRunnable, 3000); } else { mAutoDisableObserveModeRunnable.addServiceToList(name); } } } void rescheduleInactivityChecks() { mHandler.removeCallbacks(mReturnToIdleStateRunnable); if (isMultipleBindingSupported()) { mHandler.removeCallbacks(mUnbindInactiveServicesRunnable); mHandler.postDelayed(mUnbindInactiveServicesRunnable, UNBIND_SERVICES_DELAY_MS); } } private void setPollingLoopStateLocked(PollingLoopState state) { Log.d(TAG, "setPollingLoopStateLocked: " + mPollingLoopState + " -> " + state); mPollingLoopState = state; } @TargetApi(35) public void onPollingLoopDetected(List pollingFrames) { Log.d(TAG, "onPollingLoopDetected: size: " + pollingFrames.size()); synchronized (mLock) { rescheduleInactivityChecks(); // We need to have this check here in addition to the one in onFieldChangeDetected, // because we can receive an OFF frame after the field change is detected. if (!pollingFrames.isEmpty() && pollingFrames.getLast().getType() == PollingFrame.POLLING_LOOP_TYPE_OFF) { mHandler.postDelayed(mReturnToIdleStateRunnable, FIELD_OFF_IDLE_DELAY_MS); } if (mState == STATE_IDLE) { mState = STATE_POLLING_LOOP; } int onCount = 0; int offCount = 0; int aCount = 0; int bCount = 0; if (mPendingPollingLoopFrames == null) { mPendingPollingLoopFrames = new ArrayList(1); } for (PollingFrame pollingFrame : pollingFrames) { if (mUnprocessedPollingFrames != null) { mUnprocessedPollingFrames.add(pollingFrame); } else if (pollingFrame.getType() == PollingFrame.POLLING_LOOP_TYPE_F) { Pair serviceAndName = getForegroundServiceAndNameOrDefault(); if (serviceAndName != null) { sendFrameToServiceLocked(serviceAndName.first, serviceAndName.second, pollingFrame); } } else if (pollingFrame.getType() == PollingFrame.POLLING_LOOP_TYPE_UNKNOWN) { byte[] data = pollingFrame.getData(); String dataStr = HexFormat.of().formatHex(data).toUpperCase(Locale.ROOT); List serviceInfos = mPollingLoopFilters.get(ActivityManager.getCurrentUser()).get(dataStr); Map> patternMappingForUser = mPollingLoopPatternFilters.get(ActivityManager.getCurrentUser()); Set patternSet = patternMappingForUser.keySet(); List matchedPatterns = patternSet.stream() .filter(p -> p.matcher(dataStr).matches()).toList(); if (!matchedPatterns.isEmpty()) { if (serviceInfos == null) { serviceInfos = new ArrayList(); } for (Pattern matchedPattern : matchedPatterns) { serviceInfos.addAll(patternMappingForUser.get(matchedPattern)); } } if (serviceInfos != null && serviceInfos.size() > 0) { ApduServiceInfo serviceInfo; if (serviceInfos.size() == 1) { serviceInfo = serviceInfos.get(0); } else { serviceInfo = mAidCache.resolvePollingLoopFilterConflict(serviceInfos); if (serviceInfo == null) { /* If neither the foreground or payments service can handle the plf, * pick the first in the list. */ serviceInfo = serviceInfos.get(0); } } if (serviceInfo.getShouldAutoTransact(dataStr)) { if (mStatsdUtils != null) { mStatsdUtils.logAutoTransactReported( StatsdUtils.PROCESSOR_HOST, data); mStatsdUtils.setNextObserveModeTriggerSource( StatsdUtils.TRIGGER_SOURCE_AUTO_TRANSACT); } if (mFirmwareExitFrame != null && Arrays.equals( mFirmwareExitFrame.getData(), pollingFrame.getData())) { mFirmwareExitFrame = null; mEnableObserveModeAfterTransaction = true; Log.d(TAG, "Polling frame matches exit frame, leaving observe mode " + "disabled"); } else { allowOneTransaction(); } pollingFrame.setTriggeredAutoTransact(true); } UserHandle user = UserHandle.getUserHandleForUid(serviceInfo.getUid()); if (serviceInfo.isOnHost()) { Messenger service = bindServiceIfNeededLocked(user.getIdentifier(), serviceInfo.getComponent()); setPollingLoopStateLocked(PollingLoopState.FILTER_MATCHED); sendFrameToServiceLocked(service, serviceInfo.getComponent(), pollingFrame); } } else { Pair serviceAndName = getForegroundServiceAndNameOrDefault(); if (serviceAndName != null) { sendFrameToServiceLocked(serviceAndName.first, serviceAndName.second, pollingFrame); } } if (mStatsdUtils != null) { mStatsdUtils.tallyPollingFrame(dataStr, pollingFrame); } } else { mPendingPollingLoopFrames.add(pollingFrame); } if (mStatsdUtils != null) { mStatsdUtils.logPollingFrames(); } } mFirmwareExitFrame = null; if (mPollingLoopState == PollingLoopState.EVALUATING_POLLING_LOOP) { if (mPendingPollingLoopFrames.size() >= 3) { for (PollingFrame frame : mPendingPollingLoopFrames) { int type = frame.getType(); switch (type) { case PollingFrame.POLLING_LOOP_TYPE_A: aCount++; if (aCount > 3) { setPollingLoopStateLocked( PollingLoopState.DELIVERING_TO_PREFERRED); } break; case PollingFrame.POLLING_LOOP_TYPE_B: bCount++; if (bCount > 3) { setPollingLoopStateLocked( PollingLoopState.DELIVERING_TO_PREFERRED); } break; case PollingFrame.POLLING_LOOP_TYPE_ON: onCount++; break; case PollingFrame.POLLING_LOOP_TYPE_OFF: // Send the loop data if we've seen at least one on before an off. offCount++; if (onCount >= 2 && offCount >=2) { setPollingLoopStateLocked( PollingLoopState.DELIVERING_TO_PREFERRED); } break; default: } if (mPollingLoopState != PollingLoopState.EVALUATING_POLLING_LOOP) { break; } } } } if (mPollingLoopState == PollingLoopState.DELIVERING_TO_PREFERRED) { Pair serviceAndName = getForegroundServiceAndNameOrDefault(); if (serviceAndName != null) { sendFramesToServiceLocked(serviceAndName.first, serviceAndName.second, mPendingPollingLoopFrames); mPendingPollingLoopFrames = null; } else { Log.i(TAG, "onPollingLoopDetected: No preferred service to deliver " + "polling frames to, allowing transaction"); allowOneTransaction(); } } } } private void allowOneTransaction() { Log.d(TAG, "allowOneTransaction"); mEnableObserveModeAfterTransaction = true; NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); mHandler.post(() -> adapter.setObserveModeEnabled(false)); } /** * Observe mode was disabled in firmware, we shouldn't autotransact on the next frame. * * This assumes the exit frame will be in the next batch of processed polling frames. */ public void onObserveModeDisabledInFirmware(PollingFrame exitFrame) { mFirmwareExitFrame = exitFrame; } /** * Preferred foreground service changed */ public void onPreferredForegroundServiceChanged(ComponentNameAndUser serviceAndUser) { synchronized (mLock) { int userId = serviceAndUser.getUserId(); ComponentName service = serviceAndUser.getComponentName(); mAidCache.onPreferredForegroundServiceChanged(serviceAndUser); if (!isHostCardEmulationActivated()) { Log.d(TAG, "onPreferredForegroundServiceChanged: resetting active service"); resetActiveService(); } if (service != null) { bindServiceIfNeededLocked(userId, service); } else { unbindServiceIfNeededLocked(); } } } public void onFieldChangeDetected(boolean fieldOn) { rescheduleInactivityChecks(); if (!fieldOn) { mHandler.postDelayed(mReturnToIdleStateRunnable, FIELD_OFF_IDLE_DELAY_MS); } if (!fieldOn && mEnableObserveModeOnFieldOff && mEnableObserveModeAfterTransaction) { Log.d(TAG, "onFieldChangeDetected: re-enable observe mode"); mHandler.postDelayed(mEnableObserveModeAfterTransactionRunnable, RE_ENABLE_OBSERVE_MODE_DELAY_MS); } if (fieldOn && nfcHceLatencyEvents()) { mFieldOnTime = SystemClock.elapsedRealtime(); } } public void onHostEmulationActivated() { Log.d(TAG, "onHostEmulationActivated"); synchronized (mLock) { if (nfcHceLatencyEvents()) { Trace.beginAsyncSection(EVENT_HCE_ACTIVATED, 0); } rescheduleInactivityChecks(); // Regardless of what happens, if we're having a tap again // activity up, close it Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE); intent.setPackage(NfcInjector.getInstance().getNfcPackageName()); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); if (mState != STATE_IDLE && mState != STATE_POLLING_LOOP) { Log.e(TAG, "onHostEmulationActivated: Got activation event in non-idle state"); } mState = STATE_W4_SELECT; } } static private class UnroutableAidBugReportRunnable implements Runnable { List mUnroutedAids; UnroutableAidBugReportRunnable(String aid) { mUnroutedAids = new ArrayList(1); mUnroutedAids.add(aid); } void addAid(String aid) { mUnroutedAids.add(aid); } @Override public void run() { NfcService.getInstance().mNfcDiagnostics.takeBugReport( "NFC tap failed." + " (If you weren't using NFC, " + "no need to submit this report.)", "Couldn't route " + String.join(", ", mUnroutedAids)); } } UnroutableAidBugReportRunnable mUnroutableAidBugReportRunnable = null; public void onHostEmulationData(byte[] data) { Log.d(TAG, "onHostEmulationData"); mHandler.removeCallbacks(mEnableObserveModeAfterTransactionRunnable); rescheduleInactivityChecks(); String selectAid = findSelectAid(data); ComponentName resolvedService = null; ApduServiceInfo resolvedServiceInfo = null; AidResolveInfo resolveInfo = null; synchronized (mLock) { if (mState == STATE_IDLE) { Log.e(TAG, "onHostEmulationData: Got data in idle state."); return; } else if (mState == STATE_W4_DEACTIVATE) { Log.e(TAG, "onHostEmulationData: Dropping APDU in STATE_W4_DECTIVATE"); return; } if (selectAid != null) { if (selectAid.equals(ANDROID_HCE_AID)) { NfcService.getInstance().sendData(ANDROID_HCE_RESPONSE); return; } resolveInfo = mAidCache.resolveAid(selectAid); if (resolveInfo == null || resolveInfo.services.size() == 0) { if (selectAid.equals(NDEF_V1_AID) || selectAid.equals(NDEF_V2_AID)) { Log.w(TAG, "onHostEmulationData: Can't route NDEF AID, sending AID_NOT_FOUND"); } else if (!mPowerManager.isScreenOn()) { Log.i(TAG, "onHostEmulationData: Screen is off, sending AID_NOT_FOUND, " + "but not triggering bug report"); } else { Log.w(TAG, "onHostEmulationData: Can't handle AID " + selectAid + " sending AID_NOT_FOUND"); if (android.nfc.Flags.nfcEventListener()) { notifyAidNotRoutedListener(selectAid); } if (mUnroutableAidBugReportRunnable != null) { mUnroutableAidBugReportRunnable.addAid(selectAid); } else { mUnroutableAidBugReportRunnable = new UnroutableAidBugReportRunnable(selectAid); /* Wait 1s to see if there is an alternate AID we can route before * taking a bug report */ mHandler.postDelayed(mUnroutableAidBugReportRunnable, 1000); } } NfcInjector.getInstance().getNfcEventLog().logEvent( NfcEventProto.EventType.newBuilder() .setCeUnroutableAid( NfcEventProto.NfcCeUnroutableAid.newBuilder() .setAid(selectAid) .build()) .build()); // Tell the remote we don't handle this AID NfcService.getInstance().sendData(AID_NOT_FOUND); return; } else if (mUnroutableAidBugReportRunnable != null) { /* If there is a pending bug report runnable, cancel it. */ mHandler.removeCallbacks(mUnroutableAidBugReportRunnable); mUnroutableAidBugReportRunnable = null; } mLastSelectedAid = selectAid; if (resolveInfo.defaultService != null) { // Resolve to default // Check if resolvedService requires unlock ApduServiceInfo defaultServiceInfo = resolveInfo.defaultService; if (mStatsdUtils != null) { mStatsdUtils.setCardEmulationEventCategory(resolveInfo.category); mStatsdUtils.setCardEmulationEventUid(defaultServiceInfo.getUid()); } if ((defaultServiceInfo.requiresUnlock() || NfcService.getInstance().isSecureNfcEnabled()) && NfcInjector.getInstance().isDeviceLocked()) { NfcService.getInstance().sendRequireUnlockIntent(); NfcService.getInstance().sendData(AID_NOT_FOUND); if (DBG) Log.d(TAG, "onHostEmulationData: requiresUnlock()! show toast"); if (mStatsdUtils != null) { mStatsdUtils.logCardEmulationWrongSettingEvent(); } launchTapAgain(resolveInfo.defaultService, resolveInfo.category); return; } if (defaultServiceInfo.requiresScreenOn() && !mPowerManager.isScreenOn()) { NfcService.getInstance().sendData(AID_NOT_FOUND); if (DBG) Log.d(TAG, "onHostEmulationData: requiresScreenOn()!"); if (mStatsdUtils != null) { mStatsdUtils.logCardEmulationWrongSettingEvent(); } return; } // In no circumstance should this be an OffHostService - // we should never get this AID on the host in the first place if (!defaultServiceInfo.isOnHost()) { Log.e(TAG, "onHostEmulationData: AID that was meant to go off-host was " + "routed to host. Check routing table configuration."); NfcService.getInstance().sendData(AID_NOT_FOUND); if (mStatsdUtils != null) { mStatsdUtils.logCardEmulationNoRoutingEvent(); } return; } resolvedService = defaultServiceInfo.getComponent(); resolvedServiceInfo = defaultServiceInfo; } else if (mActiveServiceName != null) { for (ApduServiceInfo serviceInfo : resolveInfo.services) { if (mActiveServiceName.equals(serviceInfo.getComponent())) { resolvedService = mActiveServiceName; resolvedServiceInfo = serviceInfo; break; } } } if (resolvedService == null) { // We have no default, and either one or more services. // Ask the user to confirm. // Just ignore all future APDUs until we resolve to only one mState = STATE_W4_DEACTIVATE; NfcStatsLog.write(NfcStatsLog.NFC_AID_CONFLICT_OCCURRED, selectAid); if (android.nfc.Flags.nfcEventListener()) { notifyAidConflictListener(selectAid); } if (mStatsdUtils != null) { mStatsdUtils.setCardEmulationEventCategory(CardEmulation.CATEGORY_OTHER); mStatsdUtils.logCardEmulationWrongSettingEvent(); } launchResolver(selectAid, (ArrayList)resolveInfo.services, null, resolveInfo.category); return; } } switch (mState) { case STATE_W4_SELECT: if (selectAid != null) { int uid = resolvedServiceInfo.getUid(); if (mStatsdUtils != null) { mStatsdUtils.setCardEmulationEventUid(uid); mStatsdUtils.setCardEmulationEventCategory(resolveInfo.category); } UserHandle user = UserHandle.getUserHandleForUid(uid); Messenger existingService = bindServiceIfNeededLocked(user.getIdentifier(), resolvedService); if (existingService != null) { Log.d(TAG, "onHostEmulationData: Send data to existing service"); NfcInjector.getInstance().getNfcEventLog().logEvent( NfcEventProto.EventType.newBuilder() .setCeRoutedAid( NfcEventProto.NfcCeRoutedAid.newBuilder() .setAid(selectAid) .setComponentInfo( NfcEventProto.NfcComponentInfo.newBuilder() .setPackageName( resolvedService.getPackageName()) .setClassName( resolvedService.getClassName()) .build()) .build()) .build()); sendDataToServiceLocked(existingService, data); } else { // Waiting for service to be bound Log.d(TAG, "onHostEmulationData: Waiting for new service."); // Queue SELECT APDU to be used mSelectApdu = data; mState = STATE_W4_SERVICE; } if (mStatsdUtils != null) { mStatsdUtils.notifyCardEmulationEventWaitingForResponse(); } else { int statsdCategory = resolveInfo.category.equals(CardEmulation.CATEGORY_PAYMENT) ? CE_HCE_PAYMENT : CE_HCE_OTHER; NfcStatsLog.write( NfcStatsLog.NFC_CARDEMULATION_OCCURRED, statsdCategory, "HCE", uid); } } else { Log.d(TAG, "onHostEmulationData: Dropping non-select APDU " + "in STATE_W4_SELECT"); NfcService.getInstance().sendData(UNKNOWN_ERROR); } break; case STATE_W4_SERVICE: Log.d(TAG, "onHostEmulationData: Unexpected APDU in STATE_W4_SERVICE"); break; case STATE_XFER: if (selectAid != null) { UserHandle user = UserHandle.getUserHandleForUid(resolvedServiceInfo.getUid()); Messenger existingService = bindServiceIfNeededLocked(user.getIdentifier(), resolvedService); if (existingService != null) { sendDataToServiceLocked(existingService, data); } else { // Waiting for service to be bound mSelectApdu = data; mState = STATE_W4_SERVICE; } } else if (mActiveService != null) { // Regular APDU data sendDataToServiceLocked(mActiveService, data); } else { // No SELECT AID and no active service. Log.d(TAG, "onHostEmulationData: Service no longer bound, dropping APDU"); } break; } } } public void onHostEmulationDeactivated() { Log.d(TAG, "onHostEmulationDeactivated"); synchronized (mLock) { if (mState == STATE_IDLE) { Log.e(TAG, "onHostEmulationDeactivated: Got deactivation " + "event while in idle state"); } sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_LINK_LOSS); unbindServiceIfNeededLocked(); returnToIdleStateLocked(); if (mAutoDisableObserveModeRunnable != null) { mHandler.removeCallbacks(mAutoDisableObserveModeRunnable); mAutoDisableObserveModeRunnable = null; } if (mEnableObserveModeAfterTransaction) { Log.d(TAG, "onHostEmulationDeactivated: re-enable observe mode."); mHandler.postDelayed(mEnableObserveModeAfterTransactionRunnable, RE_ENABLE_OBSERVE_MODE_DELAY_MS); } if (mStatsdUtils != null) { mStatsdUtils.logCardEmulationDeactivatedEvent(); } if (nfcHceLatencyEvents()) { Trace.endAsyncSection(EVENT_HCE_ACTIVATED, 0); long endTime = SystemClock.elapsedRealtime(); long tapDuration = endTime - mFieldOnTime; if (tapDuration > mDeviceConfig.getSlowTapThresholdMillis()) { PerfettoTrigger.trigger(TRIGGER_NAME_SLOW_TAP); } } } } public boolean isHostCardEmulationActivated() { synchronized (mLock) { return mState != STATE_IDLE && mState != STATE_POLLING_LOOP; } } public void onOffHostAidSelected() { Log.d(TAG, "onOffHostAidSelected"); synchronized (mLock) { mHandler.removeCallbacks(mEnableObserveModeAfterTransactionRunnable); rescheduleInactivityChecks(); if (mState != STATE_XFER || mActiveService == null) { // Don't bother telling, we're not bound to any service yet } else { sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED); } if (mEnableObserveModeAfterTransaction) { Log.i(TAG, "onOffHostAidSelected: OffHost AID selected, " + "waiting for Field off to reenable observe mode"); mEnableObserveModeOnFieldOff = true; } resetActiveService(); unbindServiceIfNeededLocked(); mState = STATE_W4_SELECT; //close the TapAgainDialog Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE); intent.setPackage(NfcInjector.getInstance().getNfcPackageName()); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } } Messenger bindServiceIfNeededLocked(@UserIdInt int userId, ComponentName service) { if (service == null) { Log.e(TAG, "bindServiceIfNeededLocked: service ComponentName is null"); return null; } ComponentNameAndUser preferredPaymentService = mAidCache.getPreferredPaymentService(); int preferredPaymentUserId = preferredPaymentService.getUserId(); ComponentName preferredPaymentServiceName = preferredPaymentService.getComponentName(); ComponentNameAndUser newServiceAndUser = new ComponentNameAndUser(userId, service); if (mPaymentServiceName != null && mPaymentServiceName.equals(service) && mPaymentServiceUserId == userId) { Log.d(TAG, "bindServiceIfNeededLocked: Service already bound as payment service."); return mPaymentService; } else if (!mPaymentServiceBound && preferredPaymentServiceName != null && preferredPaymentServiceName.equals(service) && preferredPaymentUserId == userId) { Log.d(TAG, "bindServiceIfNeededLocked: Service should be bound as " + "payment service but is not, binding now"); bindPaymentServiceLocked(userId, preferredPaymentServiceName); return null; } else if (!isMultipleBindingSupported() && mServiceName != null && mServiceName.equals(service) && mServiceUserId == userId) { Log.d(TAG, "bindServiceIfNeededLocked: Service already bound as regular service."); return mService; } else if (isMultipleBindingSupported() && mComponentNameToConnectionsMap.containsKey(newServiceAndUser) && mComponentNameToConnectionsMap.get(newServiceAndUser).mMessenger != null) { Log.d(TAG, "bindServiceIfNeededLocked: Service" + service + " already bound as regular service."); return mComponentNameToConnectionsMap.get(newServiceAndUser).mMessenger; } else { Log.d(TAG, "bindServiceIfNeededLocked: Binding to service " + service + " for userId:" + userId); if (nfcHceLatencyEvents()) { Trace.beginAsyncSection(EVENT_HCE_BIND_SERVICE, 0); } if (mStatsdUtils != null) { mStatsdUtils.notifyCardEmulationEventWaitingForService(); } unbindServiceIfNeededLocked(); Intent aidIntent = new Intent(HostApduService.SERVICE_INTERFACE); aidIntent.setComponent(service); try { ServiceConnection connection = mConnection; if (isMultipleBindingSupported()) { connection = new HostEmulationServiceConnection(userId); mComponentNameToConnectionsMap.put( new ComponentNameAndUser(userId, service), new HostEmulationConnection(userId, service, connection)); } boolean serviceBound = mContext.bindServiceAsUser( aidIntent, connection, Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, UserHandle.of(userId)); if (!isMultipleBindingSupported()) { mServiceBound = serviceBound; } if (!serviceBound) { if (nfcHceLatencyEvents()) { Trace.endAsyncSection(EVENT_HCE_BIND_SERVICE, 0); } Log.e(TAG, "bindServiceIfNeededLocked: Could not bind service."); } else { mServiceUserId = userId; } } catch (SecurityException e) { if (nfcHceLatencyEvents()) { Trace.endAsyncSection(EVENT_HCE_BIND_SERVICE, 0); } Log.e(TAG, "bindServiceIfNeededLocked: Could not bind service " + "due to security exception."); } return null; } } private int generateApduAckCookie() { byte[] token = new byte[Integer.BYTES]; mCookieRandom.nextBytes(token); return ByteBuffer.wrap(token).getInt(); } void sendDataToServiceLocked(Messenger service, byte[] data) { mState = STATE_XFER; int cookie = 0; if (nfcHceLatencyEvents()) { cookie = generateApduAckCookie(); Trace.beginAsyncSection(EVENT_HCE_COMMAND_APDU, cookie); } if (!Objects.equals(service, mActiveService)) { sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED); mActiveService = service; if (service.equals(mPaymentService)) { mActiveServiceName = mPaymentServiceName; mActiveServiceUserId = mPaymentServiceUserId; } else { if (isMultipleBindingSupported()) { for (Map.Entry entry : mComponentNameToConnectionsMap.entrySet()) { if (service.equals(entry.getValue().mMessenger)) { mActiveServiceName = entry.getKey().getComponentName(); mActiveServiceUserId = entry.getKey().getUserId(); break; } } } else { mActiveServiceName = mServiceName; mActiveServiceUserId = mServiceUserId; } } } Message msg = Message.obtain(null, HostApduService.MSG_COMMAND_APDU); Bundle dataBundle = new Bundle(); dataBundle.putByteArray(DATA_KEY, data); msg.setData(dataBundle); msg.replyTo = mMessenger; if (nfcHceLatencyEvents()) { msg.arg1 = cookie; } try { NfcService.getInstance().notifyOemLogEvent(new OemLogItems .Builder(OemLogItems.LOG_ACTION_HCE_DATA) .setApduCommand(data) .build()); mActiveService.send(msg); } catch (RemoteException e) { if (nfcHceLatencyEvents()) { Trace.endAsyncSection(EVENT_HCE_COMMAND_APDU, cookie); } Log.e(TAG, "sendDataToServiceLocked: Remote service " + mActiveServiceName + " has died, dropping APDU", e); if (Objects.equals(mActiveService, mPaymentService)) { Log.wtf(TAG, "sendDataToServiceLocked: Rebinding payment service", e); bindPaymentServiceLocked(mPaymentServiceUserId, mLastBoundPaymentServiceName); } } } void sendPollingFramesToServiceLocked(Messenger service, ArrayList pollingFrames) { if (!Objects.equals(service, mActiveService)) { if (!isMultipleBindingSupported()) { sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED); } mActiveService = service; if (service.equals(mPaymentService)) { mActiveServiceName = mPaymentServiceName; mActiveServiceUserId = mPaymentServiceUserId; } else { mActiveServiceName = mServiceName; mActiveServiceUserId = mServiceUserId; } } Message msg = Message.obtain(null, HostApduService.MSG_POLLING_LOOP); Bundle msgData = new Bundle(); msgData.putParcelableArrayList(HostApduService.KEY_POLLING_LOOP_FRAMES_BUNDLE, pollingFrames); msg.setData(msgData); msg.replyTo = mMessenger; if (mState == STATE_IDLE) { mState = STATE_POLLING_LOOP; } if (nfcHceLatencyEvents()) { int cookie = generateApduAckCookie(); msg.arg1 = cookie; Trace.beginAsyncSection(EVENT_POLLING_FRAMES, cookie); } try { mActiveService.send(msg); } catch (RemoteException e) { Log.e(TAG, "sendPollingFramesToServiceLocked: Remote service " + mActiveServiceName + " has died, dropping frames", e); allowOneTransaction(); if (Objects.equals(mActiveService, mPaymentService)) { Log.wtf(TAG, "sendPollingFramesToServiceLocked: Rebinding payment service", e); bindPaymentServiceLocked(mPaymentServiceUserId, mLastBoundPaymentServiceName); } } } void sendDeactivateToActiveServiceLocked(int reason) { if (mActiveService == null) return; Message msg = Message.obtain(null, HostApduService.MSG_DEACTIVATED); msg.arg1 = reason; try { mActiveService.send(msg); } catch (RemoteException e) { // Don't care } } void unbindPaymentServiceLocked() { Log.d(TAG, "unbindPaymentServiceLocked"); if (mPaymentServiceBound) { try { mContext.unbindService(mPaymentConnection); if (isMultipleBindingSupported()) { mComponentNameToConnectionsMap.remove( new ComponentNameAndUser(mPaymentServiceUserId, mPaymentServiceName)); } } catch (Exception e) { Log.w(TAG, "unbindPaymentServiceLocked: Failed to unbind: " + mPaymentServiceName, e); } mPaymentServiceBound = false; } mPaymentService = null; mPaymentServiceName = null; mPaymentServiceUserId = -1; } void bindPaymentServiceLocked(@UserIdInt int userId, ComponentName serviceName) { if (nfcHceLatencyEvents()) { Trace.beginAsyncSection(EVENT_HCE_BIND_PAYMENT_SERVICE, 0); } unbindPaymentServiceLocked(); Log.d(TAG, "bindPaymentServiceLocked:" + serviceName + " for userId:" + userId); Intent intent = new Intent(HostApduService.SERVICE_INTERFACE); intent.setComponent(serviceName); try { if (isMultipleBindingSupported()) { mComponentNameToConnectionsMap.put( new ComponentNameAndUser(userId, serviceName), new HostEmulationConnection(userId, serviceName, mPaymentConnection)); } if (mContext.bindServiceAsUser(intent, mPaymentConnection, Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, UserHandle.of(userId))) { mPaymentServiceBound = true; mPaymentServiceUserId = userId; mLastBoundPaymentServiceName = serviceName; } else { if (nfcHceLatencyEvents()) { Trace.endAsyncSection(EVENT_HCE_BIND_PAYMENT_SERVICE, 0); } Log.e(TAG, "bindPaymentServiceLocked: Could not bind (persistent) " + "payment service"); } } catch (SecurityException e) { if (nfcHceLatencyEvents()) { Trace.endAsyncSection(EVENT_HCE_BIND_PAYMENT_SERVICE, 0); } Log.e(TAG, "bindPaymentServiceLocked: Could not bind service " + "due to security exception"); } } void unbindServiceIfNeededLocked() { if (isMultipleBindingSupported()) { if (mServiceName == null || CompatChanges.isChangeEnabled( DONT_IMMEDIATELY_UNBIND_SERVICES, mServiceName.getPackageName(), UserHandle.of(mServiceUserId))) { return; } if (mServiceName != null) { mComponentNameToConnectionsMap.remove( new ComponentNameAndUser(mServiceUserId, mServiceName)); } } if (mServiceBound) { Log.d(TAG, "unbindServiceIfNeededLocked: service " + mServiceName); try { mContext.unbindService(mConnection); } catch (Exception e) { Log.w(TAG, "unbindServiceIfNeededLocked: Failed to unbind " + mServiceName, e); } mServiceBound = false; } mService = null; mServiceName = null; mServiceUserId = -1; } void launchTapAgain(ApduServiceInfo service, String category) { if (mNfcOemExtensionCallback != null) { try { mNfcOemExtensionCallback.onLaunchHceTapAgainActivity(service, category); return; } catch (RemoteException e) { Log.e(TAG, "launchTapAgain: onLaunchHceTapAgainActivity failed", e); } } Intent dialogIntent = new Intent(mContext, TapAgainDialog.class); dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, category); dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, service); dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); mContext.startActivityAsUser(dialogIntent, UserHandle.getUserHandleForUid(service.getUid())); } void launchResolver(String selectedAid, ArrayList services, ComponentName failedComponent, String category) { if (mNfcOemExtensionCallback != null) { try { mNfcOemExtensionCallback.onLaunchHceAppChooserActivity( selectedAid, services, failedComponent, category); return; } catch (RemoteException e) { Log.e(TAG, "launchResolver: onLaunchHceAppChooserActivity failed", e); } } Intent intent = new Intent(mContext, AppChooserActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putParcelableArrayListExtra(AppChooserActivity.EXTRA_APDU_SERVICES, services); intent.putExtra(AppChooserActivity.EXTRA_CATEGORY, category); if (failedComponent != null) { intent.putExtra(AppChooserActivity.EXTRA_FAILED_COMPONENT, failedComponent); } mContext.startActivityAsUser(intent, UserHandle.CURRENT); } String findSelectAid(byte[] data) { if (data == null || data.length < SELECT_APDU_HDR_LENGTH + MINIMUM_AID_LENGTH) { if (DBG) Log.d(TAG, "findSelectAid: Data size too small for SELECT APDU"); return null; } // To accept a SELECT AID for dispatch, we require the following: // Class byte must be 0x00: logical channel set to zero, no secure messaging, no chaining // Instruction byte must be 0xA4: SELECT instruction // P1: must be 0x04: select by application identifier // P2: File control information is only relevant for higher-level application, // and we only support "first or only occurrence". if (data[0] == 0x00 && data[1] == INSTR_SELECT && data[2] == 0x04) { if (data[3] != 0x00) { Log.d(TAG, "findSelectAid: Selecting next, last or previous " + "AID occurrence is not supported"); } int aidLength = Byte.toUnsignedInt(data[4]); if (data.length < SELECT_APDU_HDR_LENGTH + aidLength) { return null; } return bytesToString(data, SELECT_APDU_HDR_LENGTH, aidLength); } return null; } @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) interface NfcAidRoutingListener { void onAidConflict(@NonNull String aid); void onAidNotRouted(@NonNull String aid); } @Nullable private NfcAidRoutingListener mAidRoutingListener = null; @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) void setAidRoutingListener(@Nullable NfcAidRoutingListener listener) { mAidRoutingListener = listener; } @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) private void notifyAidConflictListener(String aid) { if (mAidRoutingListener != null && aid != null) { mAidRoutingListener.onAidConflict(aid); } } @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) private void notifyAidNotRoutedListener(String aid) { if (mAidRoutingListener != null && aid != null) { mAidRoutingListener.onAidNotRouted(aid); } } private void returnToIdleStateLocked() { mPendingPollingLoopFrames = null; mPollingFramesToSend = null; mUnprocessedPollingFrames = null; resetActiveService(); setPollingLoopStateLocked(PollingLoopState.EVALUATING_POLLING_LOOP); mState = STATE_IDLE; } private void resetActiveService() { mActiveService = null; mActiveServiceName = null; mActiveServiceUserId = -1; } private ServiceConnection mPaymentConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { ComponentName paymentServiceName = new ComponentName("", ""); synchronized (mLock) { /* Preferred Payment Service has been changed. */ if (!mLastBoundPaymentServiceName.equals(name)) { Log.i(TAG, "onServiceConnected: Ignoring bound payment service, " + name + " != " + mLastBoundPaymentServiceName); return; } mPaymentServiceName = name; mPaymentService = new Messenger(service); paymentServiceName = mPaymentServiceName; if (isMultipleBindingSupported()) { if (mComponentNameToConnectionsMap.containsKey( new ComponentNameAndUser(mPaymentServiceUserId, name))) { mComponentNameToConnectionsMap.get( new ComponentNameAndUser(mPaymentServiceUserId, name)).mMessenger = mPaymentService; } else { mComponentNameToConnectionsMap.put( new ComponentNameAndUser(mPaymentServiceUserId, name), new HostEmulationConnection(mPaymentServiceUserId, name, this, mPaymentService)); } } Log.i(TAG, "onServiceConnected: Payment service bound: " + name); if (nfcHceLatencyEvents()) { Trace.endAsyncSection(EVENT_HCE_BIND_PAYMENT_SERVICE, 0); } } NfcInjector.getInstance().getNfcEventLog().logEvent( NfcEventProto.EventType.newBuilder() .setPaymentServiceBindState( NfcEventProto.NfcPaymentServiceBindState.newBuilder() .setBindState(NfcEventProto.BindState.SERVICE_CONNECTED) .setComponentInfo( NfcEventProto.NfcComponentInfo.newBuilder() .setPackageName( paymentServiceName.getPackageName()) .setClassName( paymentServiceName.getClassName()) .build()) .build()) .build()); if (mPollingFramesToSend != null && mPollingFramesToSend.containsKey(name)) { sendPollingFramesToServiceLocked(mPaymentService, mPollingFramesToSend.get(name)); mPollingFramesToSend.remove(name); if (mUnprocessedPollingFrames != null) { ArrayList unprocessedPollingFrames = mUnprocessedPollingFrames; mUnprocessedPollingFrames = null; onPollingLoopDetected(unprocessedPollingFrames); } } } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected: " + name); ComponentName paymentServiceName = new ComponentName("", ""); synchronized (mLock) { if (isMultipleBindingSupported()) { ComponentNameAndUser nameAndUser = new ComponentNameAndUser(mPaymentServiceUserId, name); mComponentNameToConnectionsMap.remove(nameAndUser); } paymentServiceName = mPaymentServiceName; mPaymentService = null; mPaymentServiceName = null; } NfcInjector.getInstance().getNfcEventLog().logEvent( NfcEventProto.EventType.newBuilder() .setPaymentServiceBindState( NfcEventProto.NfcPaymentServiceBindState.newBuilder() .setBindState(NfcEventProto.BindState.SERVICE_DISCONNECTED) .setComponentInfo( NfcEventProto.NfcComponentInfo.newBuilder() .setPackageName( paymentServiceName.getPackageName()) .setClassName( paymentServiceName.getClassName()) .build()) .build()) .build()); } @Override public void onBindingDied(ComponentName name) { Log.i(TAG, "onBindingDied: " + name); ComponentName paymentServiceName = new ComponentName("", ""); synchronized (mLock) { if (mPaymentServiceName != null) paymentServiceName = mPaymentServiceName; if (mPaymentServiceUserId >= 0) { bindPaymentServiceLocked(mPaymentServiceUserId, mLastBoundPaymentServiceName); } } NfcInjector.getInstance().getNfcEventLog().logEvent( NfcEventProto.EventType.newBuilder() .setPaymentServiceBindState( NfcEventProto.NfcPaymentServiceBindState.newBuilder() .setBindState(NfcEventProto.BindState.SERVICE_BINDING_DIED) .setComponentInfo( NfcEventProto.NfcComponentInfo.newBuilder() .setPackageName( paymentServiceName.getPackageName()) .setClassName( paymentServiceName.getClassName()) .build()) .build()) .build()); } }; class HostEmulationServiceConnection implements ServiceConnection { @UserIdInt int mUserId; HostEmulationServiceConnection(@UserIdInt int userId) { mUserId = userId; } @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mLock) { ComponentNameAndUser preferredUserAndService = mAidCache.getPreferredService(); ComponentName preferredServiceName = preferredUserAndService == null ? null : preferredUserAndService.getComponentName(); /* Service is already deactivated and not preferred, don't bind */ if (mState == STATE_IDLE && !name.equals(preferredServiceName)) { return; } Messenger messenger = new Messenger(service); if (isMultipleBindingSupported()) { ComponentNameAndUser key = new ComponentNameAndUser(mUserId, name); if (mComponentNameToConnectionsMap.containsKey(key)) { mComponentNameToConnectionsMap.get(key).mMessenger = messenger; } else { mComponentNameToConnectionsMap.put(key, new HostEmulationConnection(mUserId, name, this, messenger)); } } else { mService = messenger; mServiceName = name; mServiceBound = true; } if (nfcHceLatencyEvents()) { Trace.endAsyncSection(EVENT_HCE_BIND_SERVICE, 0); } Log.d(TAG, "onServiceConnected: Service bound: " + name); // Send pending select APDU if (mSelectApdu != null) { if (mStatsdUtils != null) { mStatsdUtils.notifyCardEmulationEventServiceBound(); } NfcInjector.getInstance().getNfcEventLog().logEvent( NfcEventProto.EventType.newBuilder() .setCeRoutedAid( NfcEventProto.NfcCeRoutedAid.newBuilder() .setAid(mLastSelectedAid == null ? "" : mLastSelectedAid) .setComponentInfo( NfcEventProto.NfcComponentInfo.newBuilder() .setPackageName( name.getPackageName()) .setClassName( name.getClassName()) .build()) .build()) .build()); sendDataToServiceLocked(messenger, mSelectApdu); mSelectApdu = null; } else if (mPollingFramesToSend != null && mPollingFramesToSend.containsKey(name)) { sendPollingFramesToServiceLocked(messenger, mPollingFramesToSend.get(name)); mPollingFramesToSend.remove(name); if (mUnprocessedPollingFrames != null) { ArrayList unprocessedPollingFrames = mUnprocessedPollingFrames; mUnprocessedPollingFrames = null; onPollingLoopDetected(unprocessedPollingFrames); } } else { Log.d(TAG, "onServiceConnected: bound with nothing to send"); } } } @Override public void onServiceDisconnected(ComponentName name) { synchronized (mLock) { Log.d(TAG, "onServiceDisconnected: unbound: " + name); if (isMultipleBindingSupported()) { ComponentNameAndUser nameAndUser = new ComponentNameAndUser(mUserId, name); mComponentNameToConnectionsMap.remove(nameAndUser); } else { mService = null; mServiceName = null; mServiceBound = false; } } } }; private ServiceConnection mConnection = new HostEmulationServiceConnection(UserHandle.CURRENT.getIdentifier()); class MessageHandler extends Handler { @Override public void handleMessage(Message msg) { synchronized(mLock) { if (mActiveService == null) { Log.d(TAG, "handleMessage: Dropping service response message; " + "service no longer active."); return; } else if (!msg.replyTo.getBinder().equals(mActiveService.getBinder())) { Log.d(TAG, "handleMessage: Dropping service response message; " + "service no longer bound."); return; } } if (msg.what == HostApduService.MSG_RESPONSE_APDU) { Bundle dataBundle = msg.getData(); if (dataBundle == null) { return; } byte[] data = dataBundle.getByteArray(DATA_KEY); if (data == null || data.length == 0) { Log.e(TAG, "handleMessage: Dropping empty R-APDU"); return; } int state; synchronized(mLock) { state = mState; } if (state == STATE_XFER) { Log.d(TAG, "handleMessage: Sending data"); NfcService.getInstance().sendData(data); if (mStatsdUtils != null) { mStatsdUtils.notifyCardEmulationEventResponseReceived(); } } else { Log.d(TAG, "handleMessage: Dropping data, wrong state " + Integer.toString(state)); } if (nfcHceLatencyEvents()) { try { Message ackMsg = Message.obtain(null, HostApduService.MSG_RESPONSE_APDU_ACK); ackMsg.arg1 = msg.arg1; ackMsg.replyTo = mMessenger; msg.replyTo.send(ackMsg); } catch (RemoteException e) { Log.e(TAG, "handleMessage: Failed to acknowledge MSG_RESPONSE_APDU", e); } } } else if (msg.what == HostApduService.MSG_UNHANDLED) { synchronized (mLock) { Log.d(TAG, "handleMessage: Received MSG_UNHANDLED"); AidResolveInfo resolveInfo = mAidCache.resolveAid(mLastSelectedAid); if (resolveInfo.services.size() > 0) { NfcStatsLog.write(NfcStatsLog.NFC_AID_CONFLICT_OCCURRED, mLastSelectedAid); if (android.nfc.Flags.nfcEventListener()) { notifyAidConflictListener(mLastSelectedAid); } launchResolver(mLastSelectedAid, (ArrayList)resolveInfo.services, mActiveServiceName, resolveInfo.category); } } } else if (msg.what == HostApduService.MSG_COMMAND_APDU_ACK) { if (nfcHceLatencyEvents()) { Trace.endAsyncSection(EVENT_HCE_COMMAND_APDU, msg.arg1); } } else if (msg.what == HostApduService.MSG_POLLING_LOOP_ACK) { if (nfcHceLatencyEvents()) { Trace.endAsyncSection(EVENT_POLLING_FRAMES, msg.arg1); } } } } static String bytesToString(byte[] bytes, int offset, int length) { final char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; char[] chars = new char[length * 2]; int byteValue; for (int j = 0; j < length; j++) { byteValue = bytes[offset + j] & 0xFF; chars[j * 2] = hexChars[byteValue >>> 4]; chars[j * 2 + 1] = hexChars[byteValue & 0x0F]; } return new String(chars); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Bound HCE-A/HCE-B services: "); if (mPaymentServiceBound) { pw.println(" payment: " + mPaymentServiceName); } if (isMultipleBindingSupported() && !mComponentNameToConnectionsMap.isEmpty()) { pw.println(" others: "); for (Map.Entry entry : mComponentNameToConnectionsMap.entrySet()) { pw.println(" " + entry.getKey()); } } else if (mServiceBound) { pw.println(" other: " + mServiceName); } } /** * Dump debugging information as a HostEmulationManagerProto * * 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. */ void dumpDebug(ProtoOutputStream proto) { if (mPaymentServiceBound) { Utils.dumpDebugComponentName( mPaymentServiceName, proto, HostEmulationManagerProto.PAYMENT_SERVICE_NAME); } // TODO make this a repeated field and return all the services if (mServiceBound) { Utils.dumpDebugComponentName( mServiceName, proto, HostEmulationManagerProto.SERVICE_NAME); } } boolean isMultipleBindingSupported() { return Flags.allowMultipleHceBindings(); } @VisibleForTesting public int getState() { return mState; } @VisibleForTesting public ServiceConnection getServiceConnection() { return mConnection; } @VisibleForTesting public ServiceConnection getPaymentConnection() { return mPaymentConnection; } @VisibleForTesting public IBinder getMessenger() { if (mActiveService != null) { return mActiveService.getBinder(); } return null; } @VisibleForTesting public Messenger getLocalMessenger() { return mMessenger; } @VisibleForTesting public ComponentName getServiceName() { return mLastBoundPaymentServiceName; } @VisibleForTesting public Boolean isServiceBounded(@UserIdInt int userId, ComponentName componentName) { if (isMultipleBindingSupported()) { return mComponentNameToConnectionsMap.containsKey( new ComponentNameAndUser(userId, componentName)); } else { return mServiceBound; } } @VisibleForTesting public Map>> getPollingLoopFilters() { return mPollingLoopFilters; } @VisibleForTesting public Map>> getPollingLoopPatternFilters() { return mPollingLoopPatternFilters; } }