Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

빠른 액세스 월렛

Android 11부터 사용할 수 있는 빠른 액세스 월렛 기능을 사용하면 전원 메뉴에서 결제 카드와 관련 패스에 바로 액세스할 수 있습니다. NFC 단말기에서 트랜잭션을 수행하기 전에 적절한 결제 수단을 선택하고 예정된 일정을 위해 항공편과 기타 패스에 빠르게 액세스할 때 주로 사용됩니다.

전원 메뉴의 빠른 액세스 월렛 기능
그림 1. 전원 메뉴의 빠른 액세스 월렛 기능

요구사양

빠른 액세스 월렛 기능을 사용하려면 기기에 NFC가 있어야 합니다. 이 기능은 기본 NFC 결제 앱의 QuickAccessWalletService에 바인딩됩니다. 따라서 기기는 NFC 호스트 기반 카드 에뮬레이션(HCE)도 지원해야 합니다.

기능 개요

빠른 액세스 월렛은 빠른 액세스 월렛 UI와 빠른 액세스 월렛 카드 제공자라는 두 부분으로 구성됩니다.

월렛 UI는 시스템 UI에서 실행되며 platform/packages/apps/QuickAccessWallet에서 구현됩니다. 패키지를 포함하여 허용 목록에 추가하고 빠른 액세스 월렛 카드 제공자를 설치하는 것 외에는 전원 메뉴에 월렛 UI를 표시하기 위해 추가로 변경해야 할 사항은 없습니다.

기본 NFC 결제 앱이 빠른 액세스 월렛 카드 제공자입니다. 사용자는 여러 개의 NFC 결제 앱을 동시에 설치할 수 있지만 기본 NFC 결제 앱만 전원 메뉴에 카드를 표시할 수 있습니다. 처음에 기본으로 설정할 NFC 결제 앱을 지정할 수 있지만 사용자가 설정에서 다른 앱을 선택할 수 있습니다. NFC 결제 앱이 하나만 설치되면 그 앱이 자동으로 기본 NFC 결제 앱이 됩니다(CardEmulationManager 참조).

구현

빠른 액세스 월렛 UI에 카드를 제공하려면 NFC 결제 앱에서 QuickAccessWalletService를 구현해야 합니다. 결제 앱에는 서비스를 광고하는 매니페스트 항목이 포함되어야 합니다.

시스템 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" />;
 </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도 호출하려고 할 것입니다. 그러면 빠른 액세스 월렛 UI가 닫히므로 사용자는 다른 요소의 방해 없이 결제 활동을 볼 수 있습니다.

QuickAccessWalletService 구현에 관한 추가 문서는 QuickAccessWalletServiceTestQuickAccessWalletService CTS 테스트를 참조하세요.

빠른 액세스 월렛 UI 사용 설정

전원 메뉴에서 빠른 액세스 월렛을 사용할 수 있도록 구성하려면 빌드에 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에는 구현해야 하는 세 가지 추상 메서드인 onWalletCardsRequested, onWalletCardSelected, onWalletDismissed가 있습니다. 아래의 시퀀스 다이어그램은 NFC 결제 바로 전에 Quick Access Wallet이 표시될 때의 호출 시퀀스를 보여줍니다.

빠른 액세스 월렛 시퀀스 다이어그램

빠른 액세스 월렛이 표시될 때의 호출 시퀀스 예
그림 2. 빠른 액세스 월렛이 표시될 때의 호출 시퀀스 예

빠른 액세스 월렛의 모든 뷰가 NFC 결제 다음에 오는 것은 아니지만 위의 그림 2는 QuickAccessWalletService의 모든 기능을 보여줍니다. 이 예에서 빠른 액세스 월렛 카드 제공자는 파란색으로 표시된 요소를 구현합니다. 이때 결제 카드가 기기의 데이터베이스에 저장되고 PaymentCardManager라는 인터페이스를 통해 액세스된다고 가정합니다. 이에 더 나아가 PaymentActivity라는 활동이 NFC 결제 결과를 표시한다고 가정합니다. 흐름은 다음과 같이 진행됩니다.

  1. 사용자가 빠른 액세스 월렛을 표시하려는 동작을 취합니다.
  2. 시스템 UI의 일부인 빠른 액세스 월렛 UI가 패키지 관리자를 확인하여 기본 NFC 결제 앱이 QuickAccessWalletService를 내보내는지 살핍니다.

    • 서비스를 내보내지 않으면 빠른 액세스 월렛이 표시되지 않습니다.
  3. 빠른 액세스 월렛 UI가 QuickAccessWalletService에 바인딩되고 onWalletCardsRequested를 호출합니다. 이 메서드가 제공 가능한 카드의 수와 크기에 관한 데이터가 포함된 요청 객체와 콜백을 취합니다. 콜백은 백그라운드 스레드에서 호출할 수 있습니다.

  4. QuickAccessWalletService가 표시할 카드를 계산한 다음 제공된 콜백에서 onSuccess 메서드를 호출합니다. 서비스가 백그라운드 스레드에서 이러한 작업을 수행하는 것이 좋습니다.

  5. 카드가 표시되면 시스템 UI가 onWalletCardSelected를 호출하여 첫 번째 카드가 선택되었음을 QuickAccessWalletService에 알립니다.

    • onWalletCardSelected는 사용자가 새 카드를 선택할 때마다 호출됩니다.
    • 현재 선택된 카드가 변경되지 않은 경우에도 onWalletCardSelected가 호출될 수 있습니다.
  6. 사용자가 빠른 액세스 월렛을 닫으면 시스템 UI가 onWalletDismissed를 호출하여 QuickAccessWalletService에 알립니다.

위의 예에서는 월렛이 표시되어 있는 동안 사용자가 NFC 결제 단말기의 범위 안으로 휴대전화를 가져다 댑니다. NFC 결제 처리를 위한 핵심 구성요소는 HostApduService입니다. 이 메서드는 NFC 리더에서 제공되는 APDU를 처리하기 위해 구현되어야 합니다(자세한 내용은 호스트 기반 카드 에뮬레이션 참조). 이때 결제 앱이 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의 매니페스트 항목에는 Android 11에 도입된 android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE 권한이 필요합니다. 시스템 UI가 보유하고 있는 서명 수준 권한이므로 시스템 UI 프로세스만 QuickAccessWalletService의 구현에 바인딩될 수 있습니다. 유의할 점은 사이드로드된 앱이 이 권한을 요구하고 Android 10 이하를 실행하는 기기에서 QuickAccessWalletService 데이터에 관한 전체 액세스 권한을 얻을 수 있다는 것입니다. 이를 방지하려면 서비스가 onCreate에서 빌드 버전을 확인하고 Android 11 이상을 실행하는 기기에서만 서비스를 사용 설정하는 것이 좋습니다. 호스트 카드 에뮬레이션 결제 서비스를 제공하는 데 필요한 이러한 권한 외에는 다른 앱 권한이 필요하지 않습니다.

기본 NFC 결제 앱이 QuickAccessWalletService를 구현하거나 내보내지 않으면 빠른 액세스 월렛 UI가 표시되지 않습니다.

설정

사용자는 설정 앱에서 빠른 액세스 월렛 기능을 사용 중지할 수 있습니다. 설정 페이지는 설정 > 시스템 > 동작 > 카드 및 패스에 있습니다.

빠른 액세스 월렛 기능을 사용 설정 또는 사용 중지하기 위한 설정 페이지
그림 3. 빠른 액세스 월렛 기능을 사용 설정 또는 사용 중지하기 위한 설정 페이지

맞춤설정

시스템 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);
  }
}

빠른 액세스 월렛 UIGlobalActionsPanelPluginPanelViewController를 구현합니다. GlobalActionsDialogcom.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();

전원 메뉴에서 빠른 액세스 월렛을 삭제하려면 시스템 빌드에서 QuickAccessWallet 대상을 생략합니다. 전원 메뉴에서 빠른 액세스 월렛은 삭제하되 다른 시스템 UI에서 제공된 뷰에 추가하려면 그 빌드 대상을 포함하고 GlobalActionsImpl에서 GlobalActionsPanelPlugin의 참조를 삭제합니다.

기본 구성 설정

빠른 액세스 월렛 공개 상태는 두 가지 보안 설정(GLOBAL_ACTIONS_PANEL_ENABLEDGLOBAL_ACTIONS_PANEL_AVAILABLE)으로 통제됩니다. AVAILABLE 설정은 설정 메뉴에서 이 기능을 사용 설정하거나 사용 중지할 수 있는지를 제어합니다. 이 설정은 WalletPluginService에 의해 true으로 설정합니다. QuickAccessWallet가 빌드에 포함되지 않으면 설정은 false으로 유지됩니다. ENABLED 설정은 기본적으로 같은 위치에서 true로 설정되지만, 사용자가 설정 메뉴에서 사용 중지할 수 있습니다. 기본 동작을 변경하려면 WalletPluginService#enableFeatureInSettings을 수정합니다.

유효성 검사

빠른 액세스 월렛 구현을 확인하려면 CTS 및 수동 테스트를 실행합니다. 포함된 robolectric 테스트도 플러그인 변경사항을 통해 진행해야 합니다.

CTS 테스트

cts/tests/quickaccesswallet에 있는 CTS 테스트를 실행합니다.

수동 테스트

빠른 액세스 월렛의 핵심 기능을 테스트하려면 NFC 결제 단말기(실제 또는 모의)와 QuickAccessWalletService(월렛 앱)를 구현하는 NFC 결제 앱이 필요합니다. 가용성, 0 상태, 카드 선택, 잠금 화면 동작 등은 반드시 테스트해야 하는 핵심 기능입니다.

가용성

  • GLOBAL_ACTIONS_PANEL_ENABLED 설정이 true이고 기본 NFC 결제 앱이 이 기능을 지원하는 경우 빠른 액세스 월렛에 액세스할 수 있습니다.
  • GLOBAL_ACTIONS_PANEL_ENABLED 설정이 false이고 기본 NFC 결제 앱이 이 기능을 지원하는 경우 빠른 액세스 월렛에 액세스할 수 없습니다.
  • GLOBAL_ACTIONS_PANEL_ENABLED 설정이 true이고 기본 NFC 결제 앱이 이 기능을 지원하지 않는 경우 빠른 액세스 월렛에 액세스할 수 없습니다.
  • GLOBAL_ACTIONS_PANEL_ENABLED 설정이 false이고 기본 NFC 결제 앱이 이 기능을 지원하지 않는 경우 빠른 액세스 월렛에 액세스할 수 없습니다.

0 상태

  • QuickAccessWalletService가 사용 설정되었고 내보내졌지만 아무런 카드를 제공하지 않으면 빠른 액세스 월렛 UI에 빈 상태 뷰가 표시됩니다.
  • 빈 상태 뷰를 클릭하면 월렛 앱이 열립니다.

    빠른 액세스 월렛 UI의 빈 상태 뷰
    그림 4. 빠른 액세스 월렛 UI의 빈 상태 뷰

0이 아닌 상태

  • 월렛 앱에서 카드를 한 개 이상 제공하는 경우 빠른 액세스 월렛 UI에 그 카드들이 표시됩니다.

    카드가 표시된 빠른 액세스 월렛 UI
    그림 5. 카드가 표시된 빠른 액세스 월렛 UI
  • 표시된 카드가 NFC 결제 수단일 때 휴대전화를 NFC 결제 단말기에 갖다 대면 그 결제 수단이 사용되고 월렛 뷰가 닫힙니다.

  • 표시된 카드를 클릭하면 월렛 뷰가 닫히고 해당 카드에 관한 자세한 활동이 열립니다.

  • QuickAccessWalletService에서 여러 장의 카드를 제공하는 경우 사용자가 카드를 스와이프할 수 있습니다.

  • 더보기 메뉴에는 두 가지 항목이 있습니다. 월렛 앱을 여는 항목과 설정에서 카드 및 패스 표시 화면을 여는 항목입니다.

잠금 상태 테스트

  • 휴대전화가 잠겨 있으면 Settings.Secure.POWER_MENU_LOCK_SHOW_CONTENT 설정에 따라 월렛 공개 상태가 제어됩니다. 이 설정은 설정 메뉴에서 조정할 수 있습니다.
  • 휴대전화가 잠겨 있고 POWER_MENU_LOCK_SHOW_CONTENTfalse이면 월렛이 표시되지 않습니다.
  • 휴대전화가 잠겨 있고 POWER_MENU_LOCK_SHOW_CONTENTtrue이면 월렛이 표시됩니다.
  • 월렛이 잠금 화면에 표시되어 있는 동안 휴대전화를 잠금 해제하면 카드가 다시 쿼리되고, 이로 인해 카드 콘텐츠가 달라질 수 있습니다.

접근성 테스트

  • 음성 안내 지원 사용자는 좌우로 스와이프하고 카드의 콘텐츠 설명을 들어 Wallet 뷰를 탐색할 수 있습니다.
  • 음성 안내 지원을 사용 설정한 상태에서 좌우로 스와이프하면 각 카드가 차례대로 선택됩니다. 음성 안내 지원 사용자는 NFC 결제 단말기에서 NFC 결제 수단을 선택하여 사용할 수 있습니다.