Android HCE 中如何以编程方式设置 NFC_PAYMENT_FOREGROUND
明确结论
普通 Android 应用无法直接修改 NFC_PAYMENT_FOREGROUND。这是受系统保护的安全设置,写入时需要 android.permission.WRITE_SECURE_SETTINGS 权限。普通第三方应用不会获得该权限,即使在 AndroidManifest.xml 中声明也没有作用。
应用可以采取以下方式处理:
- 检查系统是否允许支付类 HCE 服务取得前台优先权。
- 如果系统不允许,引导用户打开 NFC 支付设置页面,手动启用“前台优先级”或类似选项。
- 用户开启该选项后,在前台 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 类别。
备注:内容仅供参考。
