如何在 Android 中检测默认 Tap and Pay 支付方式类型?
结论
在 Android 11(API 30)及以上版本中,可以通过 CardEmulation.getRouteDestinationForPreferredPaymentService() 查询当前首选 NFC 支付服务的路由位置:
- 返回
"Host",表示使用 HCE。 - 返回
"eSE1"、"SIM1"、"UICC1"等非"Host"值,表示使用安全元件(SE)。 - 返回
null,表示当前没有可识别的首选支付服务。
Android 10 及以下版本没有公开 API 能可靠判断默认支付方式是 HCE 还是 SE。旧版本只能检查 X wallet 自己的 HostApduService 是否为默认支付服务。即使检查结果为 false,也不能据此断定当前使用的是 SE。
Android 11 及以上的检测方式
先在 AndroidManifest.xml 中声明 NFC 功能和首选支付信息权限:
<uses-feature
android:name="android.hardware.nfc.hce"
android:required="false" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.NFC_PREFERRED_PAYMENT_INFO" />
为检测结果定义明确的类型:
sealed class PaymentRoute {
data object Hce : PaymentRoute()
data class SecureElement(val routeName: String) : PaymentRoute()
data object NoPreferredService : PaymentRoute()
data class Unavailable(val cause: Throwable? = null) : PaymentRoute()
}
然后查询当前首选支付服务的路由:
import android.content.Context
import android.nfc.NfcAdapter
import android.nfc.cardemulation.CardEmulation
import android.os.Build
fun getPreferredPaymentRoute(context: Context): PaymentRoute {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return PaymentRoute.Unavailable()
}
val adapter = NfcAdapter.getDefaultAdapter(context)
?: return PaymentRoute.Unavailable()
return try {
val cardEmulation = CardEmulation.getInstance(adapter)
val route = cardEmulation
.getRouteDestinationForPreferredPaymentService()
when {
route == null -> PaymentRoute.NoPreferredService
route.equals("Host", ignoreCase = true) -> PaymentRoute.Hce
else -> PaymentRoute.SecureElement(route)
}
} catch (e: SecurityException) {
PaymentRoute.Unavailable(e)
} catch (e: UnsupportedOperationException) {
PaymentRoute.Unavailable(e)
}
}
使用检测结果时,可以在路由指向 SE 的情况下提醒用户:
when (val route = getPreferredPaymentRoute(this)) {
PaymentRoute.Hce -> {
// 当前首选支付服务路由到 Host,符合 HCE 要求
}
is PaymentRoute.SecureElement -> {
showPaymentMethodWarning(
"当前默认支付方式使用安全元件 ${route.routeName}," +
"请在系统支付设置中改为 X wallet 的 HCE 支付服务。"
)
}
PaymentRoute.NoPreferredService -> {
showPaymentMethodWarning("当前没有设置默认的 NFC 支付服务。")
}
is PaymentRoute.Unavailable -> {
// 系统版本过低、设备不支持或厂商未完整实现相关接口
}
}
不要假定安全元件的名称是固定字符串。除 "Host" 之外,其他路由名称都应作为 SE 标识展示或记录,不要只判断 "eSE"。
检查 X wallet 的 HCE 服务是否为默认服务
如果需要确认 X wallet 的 HCE 服务是否已被系统选中,可以直接检查对应的 HostApduService:
import android.content.ComponentName
import android.content.Context
import android.nfc.NfcAdapter
import android.nfc.cardemulation.CardEmulation
fun isXWalletHceServiceDefault(context: Context): Boolean {
val adapter = NfcAdapter.getDefaultAdapter(context) ?: return false
val cardEmulation = CardEmulation.getInstance(adapter)
val service = ComponentName(
context,
XWalletHostApduService::class.java
)
return cardEmulation.isDefaultServiceForCategory(
service,
CardEmulation.CATEGORY_PAYMENT
)
}
其中,XWalletHostApduService 必须是应用实际注册的 HCE 服务,例如:
<service
android:name=".XWalletHostApduService"
android:exported="true"
android:permission="android.permission.BIND_NFC_SERVICE">
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
</intent-filter>
<meta-data
android:name="android.nfc.cardemulation.host_apdu_service"
android:resource="@xml/apduservice" />
</service>
这项检查适用于所有支持 HCE 的 Android 版本,但需要正确理解返回值:
- 返回
true,表示 X wallet 的 HCE 服务是默认支付服务。 - 返回
false,只能说明该 HCE 服务不是默认服务。 - 出现
false时,可能是系统选择了 SE 或其他应用,也可能是没有默认服务,或者设备厂商使用了不同的支付管理机制。
因此,在 Android 10 及以下版本中,不应直接把 false 解释为“当前正在使用 SE”。更准确的提示是“X wallet 的 HCE 支付服务当前未设为默认”。
引导用户打开支付设置
应用不能静默修改系统默认的 Tap and Pay 服务,只能引导用户打开系统设置:
import android.content.Intent
import android.provider.Settings
fun openNfcPaymentSettings(context: Context) {
val paymentSettings = Intent(Settings.ACTION_NFC_PAYMENT_SETTINGS)
val intent = if (
paymentSettings.resolveActivity(context.packageManager) != null
) {
paymentSettings
} else {
Intent(Settings.ACTION_NFC_SETTINGS)
}
context.startActivity(intent)
}
用户从设置页面返回后,需要在 Activity.onResume() 中再次检测,因为相关设置可能已经发生变化:
override fun onResume() {
super.onResume()
if (!isXWalletHceServiceDefault(this)) {
// 更新界面,提示 HCE 服务尚未设为默认
}
}
注意事项
“默认钱包应用”“默认 NFC 支付服务”以及钱包内部选中的银行卡或支付介质,未必指同一项设置。部分厂商还会在系统钱包、SE 管理器或钱包应用内部增加一层支付方式选择。
getRouteDestinationForPreferredPaymentService() 读取的是系统 NFC 卡模拟层中首选支付服务的路由。对于钱包应用内部没有暴露给 Android NFC 框架的设置,这个方法无法保证读取到。如果设备厂商通过私有 API 管理 SE,标准 Android API 可能只能提供有限信息。遇到这种情况,需要使用厂商提供的 SDK,或者由 X wallet 在自身业务状态中记录用户的选择。
实际判断时,可以采用以下规则:
- Android 11 及以上版本:查询路由,将
"Host"视为 HCE,其他非空值视为 SE。 - 所有支持 HCE 的版本:使用
isDefaultServiceForCategory()进一步确认 X wallet 的具体 HCE 服务是否为默认服务。 - 无法确认路由时:提示“HCE 支付服务未启用或未设为默认”,不要断言当前一定使用 SE。
备注:内容仅供参考。
