PAYATHON 2026

如何在 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。

备注:内容仅供参考。