walletCards, int selectedIndex) {
this.mWalletCards = walletCards;
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index cfc6d5777e82b..be9ab11fb3a27 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -18,16 +18,16 @@ package android.service.quickaccesswallet;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.Context;
import android.content.Intent;
-import java.util.function.Consumer;
-
/**
* Facilitates accessing cards from the {@link QuickAccessWalletService}.
*
* @hide
*/
+@TestApi
public interface QuickAccessWalletClient {
/**
@@ -40,17 +40,65 @@ public interface QuickAccessWalletClient {
}
/**
- * @return true if the {@link QuickAccessWalletService} is available.
+ * @return true if the {@link QuickAccessWalletService} is available. This means that the
+ * default NFC payment application has an exported service that can provide cards to the Quick
+ * Access Wallet. However, it does not mean that (1) the call will necessarily be successful,
+ * nor does it mean that cards may be displayed at this time. Addition checks are required:
+ *
+ * - If {@link #isWalletFeatureAvailable()} is false, cards should not be displayed
+ *
- If the device is locked and {@link #isWalletFeatureAvailableWhenDeviceLocked} is
+ * false, cards should not be displayed while the device remains locked. (A message
+ * prompting the user to unlock to view cards may be appropriate).
+ *
*/
boolean isWalletServiceAvailable();
+ /**
+ * Wallet cards should not be displayed if:
+ *
+ * - The wallet service is unavailable
+ * - The device is not provisioned, ie user setup is incomplete
+ * - If the wallet feature has been disabled by the user
+ * - If the phone has been put into lockdown mode
+ *
+ *
+ * Quick Access Wallet implementers should call this method before calling
+ * {@link #getWalletCards} to ensure that cards may be displayed.
+ */
+ boolean isWalletFeatureAvailable();
+
+ /**
+ * Wallet cards may not be displayed on the lock screen if the user has opted to hide
+ * notifications or sensitive content on the lock screen.
+ *
+ * - The device is not provisioned, ie user setup is incomplete
+ * - If the wallet feature has been disabled by the user
+ * - If the phone has been put into lockdown mode
+ *
+ *
+ *
+ * Quick Access Wallet implementers should call this method before calling
+ * {@link #getWalletCards} if the device is currently locked.
+ *
+ * @return true if cards may be displayed on the lock screen.
+ */
+ boolean isWalletFeatureAvailableWhenDeviceLocked();
+
/**
* Get wallet cards from the {@link QuickAccessWalletService}.
*/
void getWalletCards(
@NonNull GetWalletCardsRequest request,
- @NonNull Consumer onSuccessListener,
- @NonNull Consumer onFailureListener);
+ @NonNull OnWalletCardsRetrievedCallback callback);
+
+ /**
+ * Callback for getWalletCards
+ */
+ interface OnWalletCardsRetrievedCallback {
+ void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response);
+
+ void onWalletCardRetrievalError(@NonNull GetWalletCardsError error);
+ }
/**
* Notify the {@link QuickAccessWalletService} service that a wallet card was selected.
@@ -65,12 +113,24 @@ public interface QuickAccessWalletClient {
/**
* Unregister event listener.
*/
- void registerWalletServiceEventListener(Consumer listener);
+ void addWalletServiceEventListener(@NonNull WalletServiceEventListener listener);
/**
* Unregister event listener
*/
- void unregisterWalletServiceEventListener(Consumer listener);
+ void removeWalletServiceEventListener(@NonNull WalletServiceEventListener listener);
+
+ /**
+ * A listener for {@link WalletServiceEvent walletServiceEvents}
+ */
+ interface WalletServiceEventListener {
+ void onWalletServiceEvent(@NonNull WalletServiceEvent event);
+ }
+
+ /**
+ * Unregister all event listeners and disconnect from the service.
+ */
+ void disconnect();
/**
* The manifest entry for the QuickAccessWalletService may also publish information about the
@@ -78,12 +138,12 @@ public interface QuickAccessWalletClient {
* application.
*/
@Nullable
- Intent getWalletActivity();
+ Intent createWalletIntent();
/**
* The manifest entry for the {@link QuickAccessWalletService} may publish the activity that
* hosts the settings
*/
@Nullable
- Intent getSettingsActivity();
+ Intent createWalletSettingsIntent();
}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index 17c287fa8eeb0..37a8703ddebe6 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -20,6 +20,7 @@ import static android.service.quickaccesswallet.QuickAccessWalletService.SERVICE
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -27,79 +28,225 @@ import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.widget.LockPatternUtils;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
-import java.util.function.Consumer;
/**
+ * Implements {@link QuickAccessWalletClient}. The client connects, performs requests, waits for
+ * responses, and disconnects automatically after a short period of time. The client may
* @hide
*/
-@SuppressWarnings("AndroidJdkLibsChecker")
-class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Handler.Callback,
- ServiceConnection {
+public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, ServiceConnection {
private static final String TAG = "QAWalletSClient";
private final Handler mHandler;
private final Context mContext;
private final Queue mRequestQueue;
- private final Map, String> mEventListeners;
+ private final Map mEventListeners;
private boolean mIsConnected;
+ /**
+ * Timeout for active service connections (1 minute)
+ */
+ private static final long SERVICE_CONNECTION_TIMEOUT_MS = 60 * 1000;
@Nullable
private IQuickAccessWalletService mService;
-
@Nullable
private final QuickAccessWalletServiceInfo mServiceInfo;
- private static final int MSG_CONNECT = 1;
- private static final int MSG_CONNECTED = 2;
- private static final int MSG_EXECUTE = 3;
- private static final int MSG_DISCONNECT = 4;
+ private static final int MSG_TIMEOUT_SERVICE = 5;
QuickAccessWalletClientImpl(@NonNull Context context) {
mContext = context.getApplicationContext();
mServiceInfo = QuickAccessWalletServiceInfo.tryCreate(context);
- mHandler = new Handler(Looper.getMainLooper(), this);
+ mHandler = new Handler(Looper.getMainLooper());
mRequestQueue = new LinkedList<>();
mEventListeners = new HashMap<>(1);
}
@Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_CONNECT:
- connectInternal();
- break;
- case MSG_CONNECTED:
- onConnectedInternal((IQuickAccessWalletService) msg.obj);
- break;
- case MSG_EXECUTE:
- executeInternal((ApiCaller) msg.obj);
- break;
- case MSG_DISCONNECT:
- disconnectInternal();
- break;
- default:
- Log.w(TAG, "Unknown what: " + msg.what);
- return false;
+ public boolean isWalletServiceAvailable() {
+ boolean available = mServiceInfo != null;
+ Log.i(TAG, "isWalletServiceAvailable: " + available);
+ return available;
+ }
+
+ @Override
+ public boolean isWalletFeatureAvailable() {
+ int currentUser = ActivityManager.getCurrentUser();
+ return checkUserSetupComplete()
+ && checkSecureSetting(Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED)
+ && !new LockPatternUtils(mContext).isUserInLockdown(currentUser);
+ }
+
+ @Override
+ public boolean isWalletFeatureAvailableWhenDeviceLocked() {
+ return checkSecureSetting(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS)
+ && checkSecureSetting(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ }
+
+ @Override
+ public void getWalletCards(
+ @NonNull GetWalletCardsRequest request,
+ @NonNull OnWalletCardsRetrievedCallback callback) {
+
+ Log.i(TAG, "getWalletCards");
+
+ if (!isWalletServiceAvailable()) {
+ callback.onWalletCardRetrievalError(new GetWalletCardsError(null, null));
+ return;
}
- return true;
+
+ BaseCallbacks serviceCallback = new BaseCallbacks() {
+ @Override
+ public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
+ mHandler.post(() -> callback.onWalletCardsRetrieved(response));
+ }
+
+ @Override
+ public void onGetWalletCardsFailure(GetWalletCardsError error) {
+ mHandler.post(() -> callback.onWalletCardRetrievalError(error));
+ }
+ };
+
+ executeApiCall(new ApiCaller("onWalletCardsRequested") {
+ @Override
+ public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ service.onWalletCardsRequested(request, serviceCallback);
+ }
+
+ @Override
+ public void onApiError() {
+ serviceCallback.onGetWalletCardsFailure(new GetWalletCardsError(null, null));
+ }
+ });
+ }
+
+ @Override
+ public void selectWalletCard(@NonNull SelectWalletCardRequest request) {
+ Log.i(TAG, "selectWalletCard");
+ if (!isWalletServiceAvailable()) {
+ return;
+ }
+ executeApiCall(new ApiCaller("onWalletCardSelected") {
+ @Override
+ public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ service.onWalletCardSelected(request);
+ }
+ });
+ }
+
+ @Override
+ public void notifyWalletDismissed() {
+ if (!isWalletServiceAvailable()) {
+ return;
+ }
+ Log.i(TAG, "notifyWalletDismissed");
+ executeApiCall(new ApiCaller("onWalletDismissed") {
+ @Override
+ public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ service.onWalletDismissed();
+ }
+ });
+ }
+
+ @Override
+ public void addWalletServiceEventListener(WalletServiceEventListener listener) {
+ if (!isWalletServiceAvailable()) {
+ return;
+ }
+ Log.i(TAG, "registerWalletServiceEventListener");
+ BaseCallbacks callback = new BaseCallbacks() {
+ @Override
+ public void onWalletServiceEvent(WalletServiceEvent event) {
+ Log.i(TAG, "onWalletServiceEvent");
+ mHandler.post(() -> listener.onWalletServiceEvent(event));
+ }
+ };
+
+ executeApiCall(new ApiCaller("registerListener") {
+ @Override
+ public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ String listenerId = UUID.randomUUID().toString();
+ WalletServiceEventListenerRequest request =
+ new WalletServiceEventListenerRequest(listenerId);
+ mEventListeners.put(listener, listenerId);
+ service.registerWalletServiceEventListener(request, callback);
+ }
+ });
+ }
+
+ @Override
+ public void removeWalletServiceEventListener(WalletServiceEventListener listener) {
+ if (!isWalletServiceAvailable()) {
+ return;
+ }
+ Log.i(TAG, "unregisterWalletServiceEventListener");
+ executeApiCall(new ApiCaller("unregisterListener") {
+ @Override
+ public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ String listenerId = mEventListeners.remove(listener);
+ if (listenerId == null) {
+ return;
+ }
+ WalletServiceEventListenerRequest request =
+ new WalletServiceEventListenerRequest(listenerId);
+ service.unregisterWalletServiceEventListener(request);
+ }
+ });
+ }
+
+ @Override
+ public void disconnect() {
+ Log.i(TAG, "disconnect");
+ mHandler.post(() -> disconnectInternal(true));
+ }
+
+ @Override
+ @Nullable
+ public Intent createWalletIntent() {
+ if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getWalletActivity())) {
+ return null;
+ }
+ return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
+ .setComponent(
+ new ComponentName(
+ mServiceInfo.getComponentName().getPackageName(),
+ mServiceInfo.getWalletActivity()));
+ }
+
+ @Override
+ @Nullable
+ public Intent createWalletSettingsIntent() {
+ if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getSettingsActivity())) {
+ return null;
+ }
+ return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET_SETTINGS)
+ .setComponent(
+ new ComponentName(
+ mServiceInfo.getComponentName().getPackageName(),
+ mServiceInfo.getSettingsActivity()));
}
private void connect() {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECT));
+ Log.i(TAG, "connect");
+ mHandler.post(this::connectInternal);
}
private void connectInternal() {
+ Log.i(TAG, "connectInternal");
if (mServiceInfo == null) {
Log.w(TAG, "Wallet service unavailable");
return;
@@ -113,15 +260,18 @@ class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Handler.Ca
intent.setComponent(mServiceInfo.getComponentName());
int flags = Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY;
mContext.bindService(intent, this, flags);
+ resetServiceConnectionTimeout();
}
private void onConnectedInternal(IQuickAccessWalletService service) {
+ Log.i(TAG, "onConnectedInternal");
if (!mIsConnected) {
Log.w(TAG, "onConnectInternal but connection closed");
mService = null;
return;
}
mService = service;
+ Log.i(TAG, "onConnectedInternal success: request queue size " + mRequestQueue.size());
for (ApiCaller apiCaller : new ArrayList<>(mRequestQueue)) {
try {
apiCaller.performApiCall(mService);
@@ -135,15 +285,33 @@ class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Handler.Ca
}
}
- private void disconnect() {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_DISCONNECT));
+ /**
+ * Resets the idle timeout for this connection by removing any pending timeout messages and
+ * posting a new delayed message.
+ */
+ private void resetServiceConnectionTimeout() {
+ Log.i(TAG, "resetServiceConnectionTimeout");
+ mHandler.removeMessages(MSG_TIMEOUT_SERVICE);
+ mHandler.postDelayed(
+ () -> disconnectInternal(true),
+ MSG_TIMEOUT_SERVICE,
+ SERVICE_CONNECTION_TIMEOUT_MS);
}
- private void disconnectInternal() {
+ private void disconnectInternal(boolean clearEventListeners) {
+ Log.i(TAG, "disconnectInternal: " + clearEventListeners);
if (!mIsConnected) {
Log.w(TAG, "already disconnected");
return;
}
+ if (clearEventListeners && !mEventListeners.isEmpty()) {
+ Log.i(TAG, "disconnectInternal: clear event listeners");
+ for (WalletServiceEventListener listener : mEventListeners.keySet()) {
+ removeWalletServiceEventListener(listener);
+ }
+ mHandler.post(() -> disconnectInternal(false));
+ return;
+ }
mIsConnected = false;
mContext.unbindService(/*conn=*/this);
mService = null;
@@ -151,181 +319,81 @@ class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Handler.Ca
mRequestQueue.clear();
}
- private void execute(ApiCaller apiCaller) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_EXECUTE, apiCaller));
+ private void executeApiCall(ApiCaller apiCaller) {
+ Log.i(TAG, "execute: " + apiCaller.mDesc);
+ mHandler.post(() -> executeInternal(apiCaller));
}
private void executeInternal(ApiCaller apiCall) {
+ Log.i(TAG, "executeInternal: " + apiCall.mDesc);
if (mIsConnected && mService != null) {
try {
apiCall.performApiCall(mService);
+ Log.i(TAG, "executeInternal success: " + apiCall.mDesc);
+ resetServiceConnectionTimeout();
} catch (RemoteException e) {
- Log.w(TAG, "executeInternal error", e);
+ Log.w(TAG, "executeInternal error: " + apiCall.mDesc, e);
apiCall.onApiError();
disconnect();
}
} else {
+ Log.i(TAG, "executeInternal: queued" + apiCall.mDesc);
mRequestQueue.add(apiCall);
connect();
}
}
- public boolean isWalletServiceAvailable() {
- return mServiceInfo != null;
- }
-
private abstract static class ApiCaller {
+ private final String mDesc;
+
+ private ApiCaller(String desc) {
+ this.mDesc = desc;
+ }
+
abstract void performApiCall(IQuickAccessWalletService service) throws RemoteException;
void onApiError() {
- Log.w(TAG, "api error");
+ Log.w(TAG, "api error: " + mDesc);
}
}
- public void getWalletCards(
- @NonNull GetWalletCardsRequest request,
- @NonNull Consumer onSuccessListener,
- @NonNull Consumer onFailureListener) {
-
- BaseCallbacks callback = new BaseCallbacks() {
- @Override
- public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
- mHandler.post(() -> onSuccessListener.accept(response));
- }
-
- @Override
- public void onGetWalletCardsFailure(GetWalletCardsError error) {
- mHandler.post(() -> onFailureListener.accept(error));
- }
- };
-
- execute(new ApiCaller() {
- @Override
- public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
- service.onWalletCardsRequested(request, callback);
- }
-
- @Override
- public void onApiError() {
- callback.onGetWalletCardsFailure(new GetWalletCardsError(null, null));
- }
- });
- }
-
- public void selectWalletCard(@NonNull SelectWalletCardRequest request) {
- execute(new ApiCaller() {
- @Override
- public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
- service.onWalletCardSelected(request);
- }
- });
- }
-
- public void notifyWalletDismissed() {
- execute(new ApiCaller() {
- @Override
- public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
- service.onWalletDismissed();
- mHandler.sendMessage(mHandler.obtainMessage(MSG_DISCONNECT));
- }
- });
- }
-
- @Override
- public void registerWalletServiceEventListener(Consumer listener) {
-
- BaseCallbacks callback = new BaseCallbacks() {
- @Override
- public void onWalletServiceEvent(WalletServiceEvent event) {
- Log.i(TAG, "onWalletServiceEvent");
- mHandler.post(() -> listener.accept(event));
- }
- };
-
- execute(new ApiCaller() {
- @Override
- public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
- String listenerId = UUID.randomUUID().toString();
- WalletServiceEventListenerRequest request =
- new WalletServiceEventListenerRequest(listenerId);
- mEventListeners.put(listener, listenerId);
- service.registerWalletServiceEventListener(request, callback);
- }
- });
- }
-
- @Override
- public void unregisterWalletServiceEventListener(Consumer listener) {
- execute(new ApiCaller() {
- @Override
- public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
- String listenerId = mEventListeners.get(listener);
- if (listenerId == null) {
- return;
- }
- WalletServiceEventListenerRequest request =
- new WalletServiceEventListenerRequest(listenerId);
- service.unregisterWalletServiceEventListener(request);
- }
- });
- }
-
- @Override
- @Nullable
- public Intent getWalletActivity() {
- if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getWalletActivity())) {
- return null;
- }
- return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
- .setComponent(
- new ComponentName(
- mServiceInfo.getComponentName().getPackageName(),
- mServiceInfo.getWalletActivity()));
- }
-
- @Override
- @Nullable
- public Intent getSettingsActivity() {
- if (mServiceInfo == null || TextUtils.isEmpty(mServiceInfo.getSettingsActivity())) {
- return null;
- }
- return new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET_SETTINGS)
- .setComponent(
- new ComponentName(
- mServiceInfo.getComponentName().getPackageName(),
- mServiceInfo.getSettingsActivity()));
- }
-
- /**
- * Connection to the {@link QuickAccessWalletService}
- */
-
-
- @Override
+ @Override // ServiceConnection
public void onServiceConnected(ComponentName name, IBinder binder) {
+ Log.i(TAG, "onServiceConnected: " + name);
IQuickAccessWalletService service = IQuickAccessWalletService.Stub.asInterface(binder);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTED, service));
+ mHandler.post(() -> onConnectedInternal(service));
}
- @Override
+ @Override // ServiceConnection
public void onServiceDisconnected(ComponentName name) {
// Do not disconnect, as we may later be re-connected
Log.w(TAG, "onServiceDisconnected");
}
- @Override
+ @Override // ServiceConnection
public void onBindingDied(ComponentName name) {
// This is a recoverable error but the client will need to reconnect.
Log.w(TAG, "onBindingDied");
disconnect();
}
- @Override
+ @Override // ServiceConnection
public void onNullBinding(ComponentName name) {
Log.w(TAG, "onNullBinding");
disconnect();
}
+ private boolean checkSecureSetting(String name) {
+ return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) == 1;
+ }
+
+ private boolean checkUserSetupComplete() {
+ return Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 0,
+ UserHandle.USER_CURRENT) == 1;
+ }
+
private static class BaseCallbacks extends IQuickAccessWalletServiceCallbacks.Stub {
public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
throw new IllegalStateException();
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
index d968405002e30..aba5611581e31 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -45,8 +45,9 @@ import android.util.Log;
* android.permission.BIND_NFC_SERVICE permission.
* The user explicitly selected the application as the default payment application in
* the Tap & pay settings screen.
- * The application requires the {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE}
- * permission in its manifest.
+ * The QuickAccessWalletService requires that the binding application hold the
+ * {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE} permission, which only the System
+ * Service can hold.
* The user explicitly enables it using Android Settings (the
* {@link Settings#ACTION_QUICK_ACCESS_WALLET_SETTINGS} intent can be used to launch it).
*
@@ -73,6 +74,11 @@ import android.util.Log;
* a new card is selected, the Android System will notify the service through
* {@link #onWalletCardSelected} and will provide the {@link WalletCard#getCardId() cardId} of the
* card that is now selected.
+ * If the user commences an NFC payment, the service may send a {@link WalletServiceEvent}
+ * to the System indicating that the wallet application now needs to show the activity associated
+ * with making a payment. Sending a {@link WalletServiceEvent} of type
+ * {@link WalletServiceEvent#TYPE_NFC_PAYMENT_STARTED} should cause the quick access wallet UI
+ * to be dismissed.
* When the wallet is dismissed, the Android System will notify the service through
* {@link #onWalletDismissed}.
*
@@ -110,6 +116,7 @@ import android.util.Log;
* android:permission="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE">
*
*
+ *
*
* ;
@@ -165,29 +172,6 @@ public abstract class QuickAccessWalletService extends Service {
public static final String ACTION_VIEW_WALLET_SETTINGS =
"android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS";
- /**
- * Broadcast Action: Sent by the wallet application to dismiss the Quick Access Wallet.
- *
- * The Quick Access Wallet may be shown in a system window on top of other Activities. If the
- * user selects a payment card from the Quick Access Wallet and then holds their phone to an NFC
- * terminal, the wallet application will need to show a payment Activity. But if the Quick
- * Access Wallet is still being shown, it may obscure the payment Activity. To avoid this, the
- * wallet application can send a broadcast to the Android System with this action to request
- * that the Quick Access Wallet be dismissed.
- *
- * This broadcast must use the {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE}
- * permission to ensure that it is only delivered to System UI. Furthermore, your application
- * must require the {@code android.permission.DISMISS_QUICK_ACCESS_WALLET}
- *
- *
- * context.sendBroadcast(
- * new Intent(ACTION_DISMISS_WALLET), Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE);
- *
- */
- @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_DISMISS_WALLET =
- "android.service.quickaccesswallet.action.DISMISS_WALLET";
-
/**
* Name under which a QuickAccessWalletService component publishes information about itself.
* This meta-data should reference an XML resource containing a
@@ -202,8 +186,20 @@ public abstract class QuickAccessWalletService extends Service {
public static final String SERVICE_META_DATA = "android.quickaccesswallet";
private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ /**
+ * The service currently only supports one listener at a time. Multiple connections that
+ * register different listeners will clobber the listener. This field may only be accessed from
+ * the main thread.
+ */
@Nullable
private String mEventListenerId;
+
+ /**
+ * The service currently only supports one listener at a time. Multiple connections that
+ * register different listeners will clobber the listener. This field may only be accessed from
+ * the main thread.
+ */
@Nullable
private IQuickAccessWalletServiceCallbacks mEventListener;
@@ -240,7 +236,8 @@ public abstract class QuickAccessWalletService extends Service {
private void onWalletCardsRequestedInternal(
GetWalletCardsRequest request,
IQuickAccessWalletServiceCallbacks callback) {
- onWalletCardsRequested(request, new GetWalletCardsCallback(callback, mHandler));
+ onWalletCardsRequested(request,
+ new GetWalletCardsCallbackImpl(request, callback, mHandler));
}
@Override
@@ -250,14 +247,14 @@ public abstract class QuickAccessWalletService extends Service {
// Binding to the QuickAccessWalletService is protected by the
// android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE permission, which is defined in
// R. Pre-R devices can have other side-loaded applications that claim this permission.
- // This ensures that the service is only available when properly permission protected.
+ // Ensures that the service is only enabled when properly permission protected.
Log.w(TAG, "Warning: binding on pre-R device");
}
- if (SERVICE_INTERFACE.equals(intent.getAction())) {
- return mInterface.asBinder();
+ if (!SERVICE_INTERFACE.equals(intent.getAction())) {
+ Log.w(TAG, "Wrong action");
+ return null;
}
- Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
- return null;
+ return mInterface.asBinder();
}
/**
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index 8793f28bc7085..23173a820a2ee 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -77,8 +77,9 @@ class QuickAccessWalletServiceInfo {
}
if (!Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE.equals(serviceInfo.permission)) {
- Log.w(TAG, String.format("QuickAccessWalletService from %s does not have permission %s",
- serviceInfo.packageName, Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE));
+ Log.w(TAG, String.format("%s.%s does not require permission %s",
+ serviceInfo.packageName, serviceInfo.name,
+ Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE));
return null;
}
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index c3b1a4b0b85f2..e6ae0abef9181 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -174,22 +174,25 @@ public final class WalletCard implements Parcelable {
* @param cardId The card id must be non-null and unique within the list of
* cards returned. Note:
* this card ID should not contain PII (Personally
- * Identifiable Information, * such as username or email
- * address).
+ * Identifiable Information, such as username or email address).
* @param cardImage The visual representation of the card. If the card image Icon
* is a bitmap, it should have a width of {@link
* GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
* GetWalletCardsRequest#getCardHeightPx()}. If the card image
* does not have these dimensions, it may appear distorted when it
- * is scaled to fit these dimensions on screen.
+ * is scaled to fit these dimensions on screen. Bitmaps should be
+ * of type {@link android.graphics.Bitmap.Config#HARDWARE} for
+ * performance reasons.
* @param contentDescription The content description of the card image. This field is
- * required.
+ * required and may not be null or empty.
* Note: this message should not contain PII
* (Personally Identifiable Information, such as username or email
* address).
* @param pendingIntent If the user performs a click on the card, this PendingIntent
* will be sent. If the device is locked, the wallet will first
- * request device unlock before sending the pending intent.
+ * request device unlock before sending the pending intent. It is
+ * recommended that the pending intent be immutable (use {@link
+ * PendingIntent#FLAG_IMMUTABLE}).
*/
public Builder(@NonNull String cardId,
@NonNull Icon cardImage,