Android 11 提供的快速访问钱包功能允许用户直接从电源菜单访问支付卡和相关通行证。主要用例包括在 NFC 终端执行交易之前选择合适的支付方式,以及快速访问航班和其他通行证以迎接即将到来的活动。
在 Android 12 或更高版本中,快速访问钱包功能可从阴影中使用,如图 1 和图 2 所示。
在 Android 11 中,该功能可从电源菜单中使用,如图 3 所示。
要求
您的设备必须具有 NFC 才能使用快速访问钱包功能。该功能绑定到默认 NFC 支付应用程序的QuickAccessWalletService
,这意味着设备还必须支持 NFC基于主机的卡模拟 (HCE) 。
功能概述
快速访问钱包有两个部分:快速访问钱包 UI 和快速访问钱包卡提供商。
在 Android 12 或更高版本中,Wallet UI 在 System UI 中运行,位于frameworks/base/packages/SystemUI/src/com/android/systemui/wallet
中。在 Android 11 中,必须安装位于platform/packages/apps/QuickAccessWallet
中的 Wallet UI 并将其列入白名单。
快速访问钱包卡提供商是默认的 NFC 支付应用程序。用户可以同时安装多个 NFC 支付应用,但只有默认的 NFC 支付应用才能在电源菜单上显示卡片。您可以指定最初将哪个 NFC 支付应用程序设置为默认应用程序,但用户可以在“设置”中选择其他应用程序。如果只安装了一个 NFC 支付应用程序,它会自动成为默认应用程序(请参阅CardEmulationManager
)。
执行
要向 Quick Access Wallet UI 提供卡片,NFC 支付应用程序必须实现QuickAccessWalletService
。支付应用程序必须包含广告服务的清单条目。
为确保只有 System UI 可以绑定QuickAccessWalletService
,NFC 支付应用必须需要android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE
权限。要求此权限可确保只有系统 UI 可以绑定到QuickAccessWalletService
。
<service
android:name=".MyQuickAccessWalletService"
android:label="@string/my_default_tile_label"
android:icon="@drawable/my_default_icon_label"
android:logo="@drawable/my_wallet_logo"
android:permission="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE">
<intent-filter>
<action android:name="android.service.quickaccesswallet.QuickAccessWalletService" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data android:name="android.quickaccesswallet"
android:resource="@xml/quickaccesswallet_configuration" />
<meta-data
android:name="android.quickaccesswallet.tile"
android:resource="@drawable/my_default_tile_icon"/>
</service>
有关钱包的其他信息包含在链接的 XML 文件中:
<quickaccesswallet-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.example.android.SettingsActivity"
android:shortcutLongLabel="@string/my_wallet_empty_state_text"
android:shortcutShortLabel="@string/my_wallet_button_text"
android:targetActivity="com.example.android.WalletActivity"/>
接下来,支付应用程序必须实现QuickAccessWalletService
:
public class MyQuickAccessWalletService extends QuickAccessWalletService {
@Override
public void onWalletCardsRequested(
GetWalletCardsRequest request,
GetWalletCardsCallback callback) {
GetWalletCardsResponse response = // generate response
callback.onSuccess(response);
}
@Override
public void onWalletCardSelected(SelectWalletCardRequest request) {
// selecting a card should ensure that it is used when making an NFC payment
}
@Override
public void onWalletDismissed() {
// May un-select card if the wallet app has the concept of a 'default'
// payment method
}
}
如果HostApduService
开始处理 NFC 交易并因此启动一个活动来显示付款的进度和结果,它还应该尝试获取对绑定的QuickAccessWalletService
的引用并使用事件类型 TYPE_NFC_PAYMENT_STARTED 调用QuickAccessWalletService#sendEvent
TYPE_NFC_PAYMENT_STARTED
.这会导致快速访问钱包 UI 被关闭,从而使用户可以畅通无阻地查看支付活动。
有关实现QuickAccessWalletService
的其他文档,请参阅QuickAccessWalletService
和TestQuickAccessWalletService
CTS 测试。
在 Android 11 中启用快速访问钱包 UI
要将 Quick Access Wallet 配置为可从 Android 11 的电源菜单中使用,请在构建中包含QuickAccessWallet
目标,并通过将以下代码示例中的粗体行添加到overlay/frameworks/base/packages/SystemUI/res/values/config.xml
来启用globalactions.wallet
插件overlay/frameworks/base/packages/SystemUI/res/values/config.xml
文件。
<resources> ... <!-- SystemUI Plugins that can be loaded on user builds. --> <string-array name="config_pluginWhitelist" translatable="false"> <item>com.android.systemui</item> <item>com.android.systemui.plugin.globalactions.wallet</item> </string-array> </resources>
使用def_nfc_payment_component
在设置配置文件中指定默认的 NFC 支付应用程序。
默认的 NFC 支付应用程序必须公开QuickAccessWalletService
以向快速访问钱包提供卡片。如果默认的 NFC 支付应用没有导出此服务,则钱包 UI 是隐藏的。
QuickAccessWalletService 实现细节
QuickAccessWalletService
具有三个必须实现的抽象方法: onWalletCardsRequested
、 onWalletCardSelected
和onWalletDismissed
。下面的序列图说明了在 NFC 支付之前查看快速访问钱包时的调用序列。
并非快速访问钱包的所有视图都跟随 NFC 支付,但上面的图 4 说明了QuickAccessWalletService
的所有功能。在此示例中,快速访问钱包卡提供商实现了蓝色轮廓的元素。假设支付卡存储在设备上的数据库中,并通过名为PaymentCardManager
的接口进行访问。进一步假设名为PaymentActivity
的活动显示 NFC 支付的结果。流程如下:
- 用户执行手势以调出快速访问钱包。
Quick Access Wallet UI(系统 UI 的一部分)检查包管理器以查看默认 NFC 支付应用程序是否导出
QuickAccessWalletService
。- 如果服务未导出,则不会显示快速访问钱包。
Quick Access Wallet UI 绑定到
QuickAccessWalletService
并调用onWalletCardsRequested
。此方法接受一个请求对象,其中包含有关可提供的卡片数量和大小的数据以及一个回调。可以从后台线程调用回调。QuickAccessWalletService
计算它想要显示的卡片,然后在提供的回调上调用onSuccess
方法。建议服务在后台线程上执行这些操作。卡片一显示,系统 UI 就会通过调用
onWalletCardSelected
通知QuickAccessWalletService
第一张卡片已被选中。- 每次用户选择一张新卡时都会调用
onWalletCardSelected
。 - 即使当前选择的卡没有更改,也可能会调用
onWalletCardSelected
。
- 每次用户选择一张新卡时都会调用
当用户关闭快速访问钱包时,系统 UI 通过调用
onWalletDismissed
QuickAccessWalletService
在上面的示例中,用户在显示钱包时将手机带入 NFC 支付终端的范围内。处理 NFC 支付的一个关键组件是HostApduService
,必须实现它来处理 NFC 读取器提供的 APDU(有关更多信息,请参阅基于主机的卡模拟)。假设支付应用启动了一个activity来显示与NFC终端交互的进度和结果。但是,快速访问钱包 UI 显示在应用程序窗口的顶部,这意味着支付活动被快速访问钱包 UI 遮挡。为了纠正这个问题,应用程序必须通知系统 UI 应该关闭快速访问钱包 UI。它可以通过获取对绑定QuickAccessWalletService
的引用并使用事件类型TYPE_NFC_PAYMENT_STARTED
调用sendWalletServiceEvent
来实现。
QuickAccessWalletService 示例实现
/** Sample implementation of {@link QuickAccessWalletService} */
@RequiresApi(VERSION_CODES.R)
public class MyQuickAccessWalletService extends QuickAccessWalletService {
private static final String TAG = "QAWalletSvc";
private ExecutorService executor;
private PaymentCardManager paymentCardManager;
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Log.w(TAG, "Should not run on pre-R devices");
stopSelf();
return;
}
executor = Executors.newSingleThreadExecutor();
paymentCardManager = new PaymentCardManager();
}
@Override
public void onDestroy() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
}
executor.shutdownNow();
}
@Override
public void onWalletCardsRequested(
@NonNull GetWalletCardsRequest request, @NonNull GetWalletCardsCallback callback) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
}
executor.submit(
() -> {
List<PaymentCard> paymentCards = paymentCardManager.getCards();
int maxCards = Math.min(paymentCards.size(), request.getMaxCards());
List<WalletCard> walletCards = new ArrayList<>(maxCards);
int selectedIndex = 0;
int cardWidthPx = request.getCardWidthPx();
int cardHeightPx = request.getCardHeightPx();
for (int index = 0; index < maxCards; index++) {
PaymentCard paymentCard = paymentCards.get(index);
WalletCard walletCard =
new WalletCard.Builder(
paymentCard.getCardId(),
paymentCard.getCardImage(cardWidthPx, cardHeightPx),
paymentCard.getContentDescription(),
paymentCard.getPendingIntent())
.build();
walletCards.add(walletCard);
if (paymentCard.isSelected()) {
selectedIndex = index;
}
}
GetWalletCardsResponse response =
new GetWalletCardsResponse(walletCards, selectedIndex);
callback.onSuccess(response);
});
}
@Override
public void onWalletCardSelected(@NonNull SelectWalletCardRequest request) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
}
executor.submit(
() -> paymentCardManager.selectCardById(request.getCardId()));
}
@Override
public void onWalletDismissed() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
}
executor.submit(() -> {
paymentCardManager.removeCardOverrides();
});
}
}
有关QuickAccessWalletService
的更多详细信息,请参阅QuickAccessWalletService
API 参考。
权限
QuickAccessWalletService
的 manifest 条目必须需要 Android 11 中引入的android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE
权限。这是系统 UI 拥有的签名级权限,这意味着只有 System UI 进程才能绑定到QuickAccessWalletService
的实现。请注意,侧面加载的应用程序可以申请此权限,并在运行 Android 10 或更低版本的设备上获得对QuickAccessWalletService
数据的完全访问权限。为防止出现这种情况,建议该服务检查onCreate
中的构建版本,并仅在运行 Android 11 及更高版本的设备上启用该服务。除了提供主机卡模拟支付服务所需的权限外,不需要其他应用权限。
如果默认 NFC 支付应用未实现或导出QuickAccessWalletService
,则不会显示快速访问钱包 UI。
Android 12 中的设置
要从锁定屏幕启用或禁用快速访问钱包,用户可以使用设置>显示>锁定屏幕中的显示钱包切换。要在阴影中禁用钱包,用户必须在快速设置阴影中手动编辑它。
图 5.在设置的锁定屏幕页面中显示钱包切换。
Android 11 中的设置
用户可以从“设置”应用关闭快速访问钱包功能。设置页面位于Settings > System > Gestures > Cards & pass 。
定制
将快速访问钱包视图添加到系统 UI 中的另一个位置
Quick Access Wallet UI是作为系统插件构建的。尽管 AOSP 实现在GlobalActionsDialog
中使用了它(在长按时显示),但只要您保持插件接口指定的合同,您就可以将该功能移动到不同的手势后面。
public interface GlobalActionsPanelPlugin extends Plugin {
/** Invoked when the view is shown */
PanelViewController onPanelShown(Callbacks callbacks, boolean deviceLocked);
/** Callbacks for interacting with the view container */
interface Callbacks {
/** Dismisses the view */
void dismissGlobalActionsMenu();
/** Starts a PendingIntent, dismissing the keyguard if necessary. */
void startPendingIntentDismissingKeyguard(PendingIntent pendingIntent);
}
/** Provides the Quick Access Wallet view */
interface PanelViewController {
/** Returns the QuickAccessWallet view, which may take any size */
View getPanelContent();
/** Invoked when the view is dismissed */
void onDismissed();
/** Invoked when the device is either locked or unlocked. */
void onDeviceLockStateChanged(boolean locked);
}
}
快速访问钱包 UI实现了GlobalActionsPanelPlugin
和PanelViewController
。 GlobalActionsDialog
使用com.android.systemui.Dependency
获取钱包插件的实例:
GlobalActionsPanelPlugin mPanelPlugin =
Dependency.get(ExtensionController.class)
.newExtension(GlobalActionsPanelPlugin.class)
.withPlugin(GlobalActionsPanelPlugin.class)
.build()
.get();
在检查插件非空且onPanelShown
返回的PanelViewController
非空后,对话框将getPanelContent
提供的View
附加到它自己的View
,并为系统事件提供适当的回调。
// Construct a Wallet PanelViewController.
// `this` implements GlobalActionsPanelPlugin.Callbacks
GlobalActionsPanelPlugin.PanelViewController mPanelController =
mPanelPlugin.onPanelShown(this, !mKeyguardStateController.isUnlocked());
// Attach the view
FrameLayout panelContainer = findViewById(R.id.my_panel_container);
FrameLayout.LayoutParams panelParams =
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT);
panelContainer.addView(mPanelController.getPanelContent(), panelParams);
// Respond to unlock events (if the view can be accessed while the phone is locked)
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
boolean unlocked = keyguardStateController.isUnlocked()
|| keyguardStateController.canDismissLockScreen();
mPanelController.onDeviceLockStateChanged(unlocked);
}
});
// Implement GlobalActionsPanelPlugin.Callbacks
@Override
public void dismissGlobalActionsMenu() {
dismissDialog();
}
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent pendingIntent) {
mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent);
}
// Notify the wallet when the container view is dismissed
mPanelController.onDismissed();
要从电源菜单中删除快速访问钱包,请从系统构建中省略QuickAccessWallet
目标。要从电源菜单中删除快速访问钱包但将其添加到不同的系统 UI 提供的视图中,请包含构建目标并从GlobalActionsImpl
中删除对GlobalActionsPanelPlugin
的引用。
设置默认配置
安卓 12
在 Android 12 或更高版本中,快速访问钱包始终在快速设置阴影中可见。锁定屏幕中快速访问钱包的可见性由以下安全设置控制: LOCKSCREEN_SHOW_WALLET
。此设置控制快速访问钱包图标是否显示在锁定屏幕的右下角。此设置默认设置为true
,但用户可以在Settings > Display > Lock screen > Show wallet中关闭。
安卓 11
在 Android 11 中,快速访问电子钱包的可见性由两个安全设置控制: GLOBAL_ACTIONS_PANEL_ENABLED
和GLOBAL_ACTIONS_PANEL_AVAILABLE
。 AVAILABLE
设置控制是否可以在“设置”中打开和关闭该功能。此设置由WalletPluginService
设置为true
。如果QuickAccessWallet
未包含在构建中,则设置保持为false
。 ENABLED
设置在同一位置默认设置为true
,但用户可以在“设置”中关闭。要更改默认行为,请修改WalletPluginService#enableFeatureInSettings
。
验证
要验证您对快速访问钱包的实施,请运行 CTS 和手动测试。对插件的更改还应执行包含的robolectric 测试。
CTS 测试
运行位于cts/tests/quickaccesswallet
的 CTS 测试。
Android 12 的手动测试
测试快速访问钱包的核心功能需要一个NFC支付终端(真假)和一个实现QuickAccessWalletService
(钱包应用)的NFC支付应用。必须测试的核心功能包括:可用性、零状态、卡选择和锁屏行为。
可用性
- 如果默认的 NFC 支付应用程序不支持该功能,则在快速设置和锁定屏幕中都无法访问快速访问钱包。
- 如果默认 NFC 支付应用程序支持该功能,则可以在快速设置阴影中访问快速访问钱包。
- 如果默认 NFC 支付应用程序支持该功能并且
LOCKSCREEN_SHOW_WALLET
设置为true
,则可以在锁定屏幕上访问快速访问钱包。 - 如果默认 NFC 支付应用支持该功能并且
LOCKSCREEN_SHOW_WALLET
设置为false
,则无法在锁定屏幕上访问快速访问钱包。
零状态
如果
QuickAccessWalletService
已启用并导出但未提供任何卡片,则 Shade 中的磁贴将如图 7 中的示例所示。单击该磁贴将打开默认的 NFC 支付应用程序。图 7.阴影中显示默认 NFC 支付应用程序的示例图块。
单击图 8 所示的空白状态视图会打开默认的 NFC 支付应用程序。此空状态视图仅在用户在钱包中剩下一张卡时显示,从卡详细信息页面中删除该卡,然后返回到钱包视图。
锁定屏幕显示钱包图标。
图 8. Quick Access Wallet UI 中的空状态视图。
非零状态
如果钱包应用程序提供一张或多张卡片,阴影中的图块将如图 9 所示。
图 9.当钱包应用程序有一张或多张卡片时,阴影中的示例图块。
单击图块会显示卡片轮播。
锁定屏幕显示一个用于打开快速访问钱包的按钮。
图 10.显示卡片的快速访问钱包 UI。
如果显示的卡代表 NFC 支付方式,则将手机靠近 NFC 支付终端会导致使用该支付方式并关闭钱包视图。
单击显示的卡片会打开该卡片的详细活动。
如果
QuickAccessWalletService
提供多张卡片,用户可以在卡片之间滑动。溢出菜单包含一个条目:打开锁定屏幕设置,以便用户可以更改显示钱包选项。
锁定状态测试
- 如果手机被锁定,钱包在快速设置阴影中可见,如果默认支付应用程序中不存在卡,则显示添加卡,或者如果默认支付应用程序中存在卡,则解锁以使用。
- 如果手机被锁定,则锁定屏幕上的钱包可见性由
Secure.LOCKSCREEN_SHOW_WALLET
设置控制,该设置在设置中控制。 - 如果手机处于锁定状态,
LOCKSCREEN_SHOW_WALLET
为false
,且默认 NFC 支付应用中不存在卡,则钱包不会显示在锁定屏幕上。 - 如果手机被锁定,
LOCKSCREEN_SHOW_WALLET
为true
,并且默认 NFC 支付应用中不存在卡,则钱包不会显示在锁定屏幕上。 - 如果手机被锁定,
LOCKSCREEN_SHOW_WALLET
为true
,并且卡存在于默认的 NFC 支付应用程序中,则钱包显示在锁定屏幕上。 - 在锁屏界面显示钱包时解锁手机会导致卡片被重新查询,这可能会导致卡片内容不同。
可访问性测试
- Talkback 用户可以通过左右滑动和收听卡片的内容描述来导航钱包视图。
- 在启用对讲的情况下左右滑动会依次选择每张卡片。对讲用户可以在NFC支付终端上选择和使用NFC支付方式。
Android 11 的手动测试
测试快速访问钱包的核心功能需要一个NFC支付终端(真假)和一个实现QuickAccessWalletService
(钱包应用)的NFC支付应用。必须测试的核心功能包括可用性、零状态、卡选择和锁定屏幕行为。
可用性
- 如果
GLOBAL_ACTIONS_PANEL_ENABLED
设置为true
并且默认 NFC 支付应用支持该功能,则可以访问快速访问钱包。 - 如果
GLOBAL_ACTIONS_PANEL_ENABLED
设置为false
并且默认 NFC 支付应用支持该功能,则无法访问快速访问钱包。 - 如果
GLOBAL_ACTIONS_PANEL_ENABLED
设置为true
并且默认 NFC 支付应用程序不支持该功能,则无法访问快速访问钱包。 - 如果
GLOBAL_ACTIONS_PANEL_ENABLED
设置为false
并且默认 NFC 支付应用程序不支持该功能,则无法访问快速访问钱包。
零状态
- 如果
QuickAccessWalletService
已启用并导出但未提供任何卡片,则快速访问钱包 UI 将显示空状态视图。 单击空白状态视图会打开钱包应用程序。
非零状态
如果钱包应用程序提供一张或多张卡片,卡片会显示在快速访问钱包 UI 中。
如果显示的卡代表 NFC 支付方式,则将手机靠近 NFC 支付终端会导致使用该支付方式并关闭钱包视图。
单击显示的卡片会关闭钱包视图并打开该卡片的详细活动。
如果
QuickAccessWalletService
提供多张卡片,用户可以在卡片之间滑动。溢出菜单包含两个条目:一个打开钱包应用程序,另一个打开设置中的显示卡和通行证屏幕。
锁定状态测试
- 如果手机被锁定,钱包可见性由
Settings.Secure.POWER_MENU_LOCK_SHOW_CONTENT
设置控制,可以在设置中控制。 - 如果手机被锁定并且
POWER_MENU_LOCK_SHOW_CONTENT
为false
,则不会显示钱包。 - 如果手机被锁定并且
POWER_MENU_LOCK_SHOW_CONTENT
为true
,则显示钱包。 - 在锁屏显示钱包时解锁手机会导致重新查询卡片,这可能会导致卡片内容不同。
可访问性测试
- TalkBack 用户可以通过左右滑动和收听卡片的内容描述来导航钱包视图。
- 在启用 TalkBack 的情况下左右滑动会依次选择每张卡片。 TalkBack 用户可以在 NFC 支付终端上选择和使用 NFC 支付方式。