快捷访问钱包

快捷访问钱包功能从 Android 11 开始提供,用户可以直接从电源菜单访问支付卡和相关通行证。主要用例包括在 NFC 终端进行交易之前选择合适的支付方式,以及快速访问即将到来的航班和其他活动的通行证。

在 Android 12 或更高版本中,如图 1 和图 2 所示,可以从阴影区域访问快捷访问钱包功能。

Quick Access Wallet feature in shade
图 1. 快捷访问钱包功能(设备已锁定)。
Quick Access Wallet feature in shade
图 2. 快捷访问钱包功能(设备已解锁)。

在 Android 11 中,如图 3 所示,可以从电源菜单访问该功能。

Quick Access Wallet feature in power menu
图 3. 电源菜单中的快捷访问钱包功能。

要求

您的设备必须具有 NFC 才能使用快捷访问钱包功能。此功能绑定到默认 NFC 支付应用的 QuickAccessWalletService,这意味着设备还必须支持 NFC 基于主机的卡模拟 (HCE)

功能概览

快捷访问钱包包含两个部分:快捷访问钱包 UI 和快捷访问钱包卡提供程序。

在 Android 12 或更高版本中,钱包 UI 在 System UI 中运行,位于 frameworks/base/packages/SystemUI/src/com/android/systemui/wallet 中。在 Android 11 中,钱包 UI 位于 platform/packages/apps/QuickAccessWallet 中,必须安装并加入白名单。

快捷访问钱包卡提供商是默认的 NFC 付款应用。用户可以同时安装多个 NFC 付款应用,但只有默认 NFC 付款应用可以在电源菜单中显示卡片。您可以指定哪个 NFC 付款应用最初设置为默认应用,但用户可以在“设置”中选择其他应用。如果仅安装了一个 NFC 付款应用,则它会自动成为默认应用(请参阅 CardEmulationManager)。

实现

要向快捷访问钱包 UI 提供卡片,NFC 付款应用必须实现 QuickAccessWalletService。付款应用必须包含一个声明该服务的清单条目。

为了确保只有 System UI 可以绑定到 QuickAccessWalletService,NFC 付款应用必须请求 android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE 权限。请求此权限可确保只有 System 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 交易,并因此启动一个 Activity 来显示付款的进度和结果,它还应尝试获取对绑定的 QuickAccessWalletService 的引用,并使用 TYPE_NFC_PAYMENT_STARTED 事件类型调用 QuickAccessWalletService#sendEvent。这会导致快捷访问钱包 UI 被关闭,从而让用户可以清楚地看到付款 Activity。

有关实现 QuickAccessWalletService 的更多文档,请参阅 QuickAccessWalletServiceTestQuickAccessWalletService CTS 测试。

在 Android 11 中启用快捷访问钱包 UI

要在 Android 11 中配置从电源菜单访问快捷访问钱包,请在 build 中包含 QuickAccessWallet 目标,并通过在下面的代码示例中以粗体添加行到 overlay/frameworks/base/packages/SystemUI/res/values/config.xml 文件来启用 globalactions.wallet 插件。

<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 具有三个必须实现的抽象方法:onWalletCardsRequestedonWalletCardSelectedonWalletDismissed。下图说明了紧接 NFC 付款之前查看快捷访问钱包时的调用顺序。

Quick Access Wallet sequence diagram

Example call sequence when Quick Access Wallet is viewed
图 4. 查看快捷访问钱包时的示例调用顺序。

并非所有快捷访问钱包的视图都紧随 NFC 付款之后,但上面的图 4 说明了 QuickAccessWalletService 的所有功能。在此示例中,快捷访问钱包卡提供商实现了蓝色轮廓中列出的元素。假设付款卡存储在设备上的数据库中,并通过名为 PaymentCardManager 的接口进行访问。进一步假设名为 PaymentActivity 的 Activity 显示 NFC 付款的结果。流程如下:

  1. 用户执行手势以调出快捷访问钱包。
  2. 快捷访问钱包 UI(System UI 的一部分)检查软件包管理器,以查看默认 NFC 付款应用是否导出 QuickAccessWalletService

    • 如果未导出该服务,则不会显示快捷访问钱包。
  3. 快捷访问钱包 UI 绑定到 QuickAccessWalletService 并调用 onWalletCardsRequested。此方法采用一个请求对象,其中包含有关可以提供的卡片的数量和大小的数据以及一个回调。可以从后台线程调用回调。

  4. QuickAccessWalletService 计算它要显示的卡片,然后在提供的回调上调用 onSuccess 方法。建议该服务在后台线程上执行这些操作。

  5. 卡片显示后,System UI 立即通过调用 onWalletCardSelected 通知 QuickAccessWalletService 第一张卡片已被选中。

    • 每次用户选择新卡片时,都会调用 onWalletCardSelected
    • 即使当前选定的卡片没有更改,也可能会调用 onWalletCardSelected
  6. 当用户关闭快捷访问钱包时,System UI 通过调用 onWalletDismissed 通知 QuickAccessWalletService

在上面的示例中,用户在显示钱包时将手机置于 NFC 付款终端的范围内。处理 NFC 付款的关键组件是 HostApduService,必须实现该组件才能处理 NFC 读取器提供的 APDU(有关更多信息,请参阅基于主机的卡模拟)。假设付款应用启动一个 Activity 以显示与 NFC 终端交互的进度和结果。但是,快捷访问钱包 UI 显示在应用窗口的顶部,这意味着付款 Activity 被快捷访问钱包 UI 遮挡。为了纠正此问题,应用必须通知 System 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 的清单条目必须请求 Android 11 中引入的 android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE 权限。这是一个签名级别的权限,由 System UI 持有,这意味着只有 System UI 进程可以绑定到 QuickAccessWalletService 的实现。请注意,侧载应用可以声明此权限,并在运行 Android 10 或更低版本的设备上获得对 QuickAccessWalletService 数据的完全访问权限。为了防止这种情况,建议服务在 onCreate 中检查 build 版本,并且仅在运行 Android 11 及更高版本的设备上启用该服务。除了提供基于主机的卡模拟付款服务所需的权限外,不需要其他应用权限。

如果默认 NFC 付款应用未实现或导出 QuickAccessWalletService,则不会显示快捷访问钱包 UI。

Android 12 中的设置

要从锁定屏幕启用或停用快捷访问钱包,用户可以使用设置 > 显示 > 锁定屏幕中的显示钱包开关。要在阴影区域中停用钱包,用户必须在快捷设置阴影区域中手动编辑它。

Toggle to enable or disable wallet from lock screen

图 5. “设置”应用的“锁定屏幕”页面中的“显示钱包”开关。

Android 11 中的设置

用户可以从“设置”应用中关闭快捷访问钱包功能。“设置”页面位于设置 > 系统 > 手势 > 卡券和凭据中。

Settings page to enable or disable the Quick Access Wallet feature
图 6. 用于启用或停用快捷访问钱包功能的“设置”页面。

自定义

将快捷访问钱包视图添加到 System UI 中的其他位置

快捷访问钱包 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 实现了 GlobalActionsPanelPluginPanelViewControllerGlobalActionsDialog 通过使用 com.android.systemui.Dependency 获取钱包插件的实例

GlobalActionsPanelPlugin mPanelPlugin =
    Dependency.get(ExtensionController.class)
        .newExtension(GlobalActionsPanelPlugin.class)
        .withPlugin(GlobalActionsPanelPlugin.class)
        .build()
        .get();

在检查插件为非 null 且 onPanelShown 返回的 PanelViewController 为非 null 之后,对话框会将 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();

要从电源菜单中移除快捷访问钱包,请从系统 build 中省略 QuickAccessWallet 目标。要从电源菜单中移除快捷访问钱包,但将其添加到不同的 System UI 提供的视图中,请包含 build 目标,并从 GlobalActionsImpl 中移除对 GlobalActionsPanelPlugin 的引用。

设置默认配置

Android 12

在 Android 12 或更高版本中,快捷访问钱包始终在快捷设置阴影区域中可见。锁定屏幕中快捷访问钱包的可见性由以下安全设置控制:LOCKSCREEN_SHOW_WALLET。此设置控制是否在锁定屏幕的右下角显示快捷访问钱包图标。默认情况下,此设置设置为 true,但用户可以在设置 > 显示 > 锁定屏幕 > 显示钱包中将其关闭。

Android 11

在 Android 11 中,快捷访问钱包可见性由两个安全设置控制:GLOBAL_ACTIONS_PANEL_ENABLEDGLOBAL_ACTIONS_PANEL_AVAILABLEAVAILABLE 设置控制是否可以在“设置”中启用和停用该功能。此设置由 WalletPluginService 设置为 true。如果 build 中未包含 QuickAccessWallet,则该设置仍为 falseENABLED 设置在同一位置默认设置为 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 已启用并导出,但不提供任何卡片,则阴影区域中的图块将如图 7 中的示例所示。点击该图块将打开默认 NFC 付款应用。

    Example tile in the shade showing default NFC payment app

    图 7. 阴影区域中显示默认 NFC 付款应用的示例图块。

  • 点击图 8 中所示的空状态视图将打开默认 NFC 付款应用。仅当用户在钱包中剩下一张卡片、从卡片详情页面中移除该卡片,然后返回到钱包视图时,才会显示此空状态视图。

  • 锁定屏幕显示钱包图标。

Empty state view in the Quick Access Wallet

图 8. 快捷访问钱包 UI 中的空状态视图。

非零状态

  • 如果钱包应用提供一张或多张卡片,则阴影区域中的图块将如图 9 所示。

    Example tile in the shade when wallet app has one or more cards

    图 9. 钱包应用有一张或多张卡片时阴影区域中的示例图块。

  • 点击该图块将显示卡片轮播界面。

  • 锁定屏幕显示一个按钮,用于打开快捷访问钱包。

    Quick Access Wallet UI with a card displayed

    图 10. 显示卡片的快捷访问钱包 UI。

  • 如果显示的卡片代表 NFC 付款方式,则将手机靠近 NFC 付款终端会导致使用该付款方式,并且钱包视图将被关闭。

  • 点击显示的卡片将打开该卡片的详细 Activity。

  • 如果 QuickAccessWalletService 提供了多张卡片,则用户可以在卡片之间滑动。

  • 溢出菜单包含一个条目:打开锁定屏幕设置,以便用户可以更改显示钱包选项。

锁定状态测试

  • 如果手机已锁定,则钱包在快捷设置阴影区域中可见,如果默认付款应用中没有卡片,则描述为添加卡片;如果默认付款应用中存在卡片,则描述为解锁以使用
  • 如果手机已锁定,则锁定屏幕上的钱包可见性由 Secure.LOCKSCREEN_SHOW_WALLET 设置控制,该设置在“设置”中控制。
  • 如果手机已锁定,LOCKSCREEN_SHOW_WALLETfalse,并且默认 NFC 付款应用中没有卡片,则锁定屏幕上不显示钱包。
  • 如果手机已锁定,LOCKSCREEN_SHOW_WALLETtrue,并且默认 NFC 付款应用中没有卡片,则锁定屏幕上不显示钱包。
  • 如果手机已锁定,LOCKSCREEN_SHOW_WALLETtrue,并且默认 NFC 付款应用中存在卡片,则锁定屏幕上显示钱包。
  • 在锁定屏幕上显示钱包时解锁手机会导致重新查询卡片,这可能会导致不同的卡片内容。

无障碍功能测试

  • Talkback 用户可以通过左右滑动并收听卡片的内容描述来导航钱包视图。
  • 启用 Talkback 后左右滑动将依次选择每张卡片。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 将显示空状态视图。
  • 点击空状态视图将打开钱包应用。

    Empty state view in the Quick Access Wallet UI
    图 11. 快捷访问钱包 UI 中的空状态视图。

非零状态

  • 如果钱包应用提供一张或多张卡片,则卡片将显示在快捷访问钱包 UI 中。

    Quick Access Wallet UI with a card displayed
    图 12. 显示卡片的快捷访问钱包 UI。
  • 如果显示的卡片代表 NFC 付款方式,则将手机靠近 NFC 付款终端会导致使用该付款方式,并且钱包视图将被关闭。

  • 点击显示的卡片将关闭钱包视图,并打开该卡片的详细 Activity。

  • 如果 QuickAccessWalletService 提供了多张卡片,则用户可以在卡片之间滑动。

  • 溢出菜单包含两个条目:一个用于打开钱包应用,另一个用于打开“设置”中的卡券和凭据屏幕。

锁定状态测试

  • 如果手机已锁定,则钱包可见性由 Settings.Secure.POWER_MENU_LOCK_SHOW_CONTENT 设置控制,该设置可以在“设置”中控制。
  • 如果手机已锁定且 POWER_MENU_LOCK_SHOW_CONTENTfalse,则不显示钱包。
  • 如果手机已锁定且 POWER_MENU_LOCK_SHOW_CONTENTtrue,则显示钱包。
  • 在锁定屏幕上显示钱包时解锁手机会导致重新查询卡片,这可能会导致不同的卡片内容。

无障碍功能测试

  • TalkBack 用户可以通过左右滑动并收听卡片的内容描述来导航钱包视图。
  • 启用 TalkBack 后左右滑动将依次选择每张卡片。TalkBack 用户可以在 NFC 付款终端选择和使用 NFC 付款方式。