PAYATHON 2026

Android HCE 中如何以编程方式设置 NFC_PAYMENT_FOREGROUND

支付老李

明确结论

普通 Android 应用无法直接修改 NFC_PAYMENT_FOREGROUND。这是受系统保护的安全设置,写入时需要 android.permission.WRITE_SECURE_SETTINGS 权限。普通第三方应用不会获得该权限,即使在 AndroidManifest.xml 中声明也没有作用。

应用可以采取以下方式处理:

  1. 检查系统是否允许支付类 HCE 服务取得前台优先权。
  2. 如果系统不允许,引导用户打开 NFC 支付设置页面,手动启用“前台优先级”或类似选项。
  3. 用户开启该选项后,在前台 Activity 中调用 CardEmulation.setPreferredService()。

为什么不能直接修改

CardEmulation.categoryAllowsForegroundPreference(CardEmulation.CATEGORY_PAYMENT) 的返回结果取决于系统的 NFC 支付策略。在部分 Android 系统中,这项策略对应:

Settings.Secure.NFC_PAYMENT_FOREGROUND

Settings.Secure 中的值受系统保护。普通应用执行下面的代码通常会失败:

Settings.Secure.putInt(
    context.getContentResolver(),
    Settings.Secure.NFC_PAYMENT_FOREGROUND,
    1
);

即使应用声明了以下权限:

<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />

系统也不会自动授予写入权限。该权限通常只提供给系统应用、特权应用,或者由开发环境通过 ADB 明确授权的应用,因此不能作为面向普通用户的产品方案。

另外,并非所有 Android 版本和厂商系统都将 NFC_PAYMENT_FOREGROUND 作为稳定接口开放给第三方应用,不应依赖它进行直接读写。

推荐处理方式

应用应先检查当前策略。如果支付类别不允许前台优先,就打开系统的 NFC 支付设置页面,让用户自行修改:

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.cardemulation.CardEmulation;
import android.provider.Settings;

public final class PaymentPreferenceHelper {

    public static boolean requestPreference(
            Activity activity,
            ComponentName paymentService
    ) {
        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity);
        if (adapter == null) {
            return false;
        }

        CardEmulation cardEmulation = CardEmulation.getInstance(adapter);

        boolean allowed = cardEmulation.categoryAllowsForegroundPreference(
                CardEmulation.CATEGORY_PAYMENT
        );

        if (!allowed) {
            openNfcPaymentSettings(activity);
            return false;
        }

        return cardEmulation.setPreferredService(activity, paymentService);
    }

    private static void openNfcPaymentSettings(Activity activity) {
        Intent intent = new Intent(Settings.ACTION_NFC_PAYMENT_SETTINGS);

        if (intent.resolveActivity(activity.getPackageManager()) != null) {
            activity.startActivity(intent);
        } else {
            activity.startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
        }
    }

    private PaymentPreferenceHelper() {
    }
}

这里的 paymentService 必须指向应用中已经注册的 HostApduService:

ComponentName paymentService = new ComponentName(
        this,
        MyPaymentService.class
);

boolean preferred = PaymentPreferenceHelper.requestPreference(
        this,
        paymentService
);

用户从系统设置返回后,可以在 onResume() 中重新检查策略并设置首选服务:

@Override
protected void onResume() {
    super.onResume();

    NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
    if (adapter == null) {
        return;
    }

    CardEmulation cardEmulation = CardEmulation.getInstance(adapter);

    if (cardEmulation.categoryAllowsForegroundPreference(
            CardEmulation.CATEGORY_PAYMENT
    )) {
        ComponentName service = new ComponentName(
                this,
                MyPaymentService.class
        );

        cardEmulation.setPreferredService(this, service);
    }
}

Activity 离开前台时,可以取消这个临时首选服务:

@Override
protected void onPause() {
    NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);

    if (adapter != null) {
        CardEmulation.getInstance(adapter)
                .unsetPreferredService(this);
    }

    super.onPause();
}

调试设备上的临时方案

在自有测试设备上,可以尝试通过 ADB 为调试应用授予安全设置写权限:

adb shell pm grant com.example.app android.permission.WRITE_SECURE_SETTINGS

获得权限后,再通过 Settings.Secure.putInt() 修改对应的值。不过,这种方式只适用于开发环境或专用设备,并有以下限制:

  • 普通用户无法自行完成授权。
  • 某些系统会拒绝授权,或者限制相关设置。
  • NFC_PAYMENT_FOREGROUND 可能不是公开 SDK 中能够直接引用的常量。
  • 不同厂商可能采用不同的 NFC 支付设置和策略。
  • 上架应用不应将这种授权设为正常运行的前提。

如果设备由企业统一管理,或者应用作为系统应用或特权应用预装,可以结合设备固件和管理策略,评估是否从系统侧修改这项设置。

注意事项

  • 正确的方法名是 setPreferredService(),不是 setPreferedPaymentService()。
  • 调用时传入的 Activity 必须位于前台,通常应在 onResume() 之后执行。
  • setPreferredService() 设置的是当前前台 Activity 使用的临时首选 HCE 服务,并不会修改系统默认支付应用。
  • 对于 CardEmulation.CATEGORY_PAYMENT,系统可能要求用户明确允许前台应用覆盖默认支付服务。
  • 不同厂商对 NFC 支付设置页面和前台优先策略的实现可能不同。如果找不到设置入口,应用应向用户说明情况,不要反复尝试写入安全设置。
  • 非支付类别可能采用不同的 categoryAllowsForegroundPreference() 处理规则,不能把支付类别的结论直接套用到所有 AID 类别。

备注:内容仅供参考。