Add QuickAccessWallet API
Adds a new API that allows applications to provide payment cards and
other relevant passes to SysUI which are then shown in the Quick Access
Wallet (long press on Pixel). See go/aospqaw-dd for details.
Bug: 144342153
Test: manual - started device, didn't blow up
Test: atest - run from frameworks/base dir
Change-Id: I8fef3116e6e4bd1f8a4f5a907892ea8993b49b0e
diff --git a/api/current.txt b/api/current.txt
index d8fac42..c1bb19e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -39,6 +39,7 @@
field public static final String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
field public static final String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+ field public static final String BIND_QUICK_ACCESS_WALLET_SERVICE = "android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE";
field public static final String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
field public static final String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
field public static final String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
@@ -39903,6 +39904,7 @@
field public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS";
field public static final String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI = "android.settings.PROCESS_WIFI_EASY_CONNECT_URI";
+ field public static final String ACTION_QUICK_ACCESS_WALLET_SETTINGS = "android.settings.QUICK_ACCESS_WALLET_SETTINGS";
field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
@@ -42946,6 +42948,94 @@
}
+package android.service.quickaccesswallet {
+
+ public final class GetWalletCardsCallback {
+ method public void onFailure(@NonNull android.service.quickaccesswallet.GetWalletCardsError);
+ method public void onSuccess(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse);
+ }
+
+ public final class GetWalletCardsError implements android.os.Parcelable {
+ ctor public GetWalletCardsError(@Nullable android.graphics.drawable.Icon, @Nullable CharSequence);
+ method public int describeContents();
+ method @Nullable public android.graphics.drawable.Icon getIcon();
+ method @Nullable public CharSequence getMessage();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.GetWalletCardsError> CREATOR;
+ }
+
+ public final class GetWalletCardsRequest implements android.os.Parcelable {
+ ctor public GetWalletCardsRequest(int, int, int, int);
+ method public int describeContents();
+ method public int getCardHeightPx();
+ method public int getCardWidthPx();
+ method public int getIconSizePx();
+ method public int getMaxCards();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.GetWalletCardsRequest> CREATOR;
+ }
+
+ public final class GetWalletCardsResponse implements android.os.Parcelable {
+ ctor public GetWalletCardsResponse(@NonNull java.util.List<android.service.quickaccesswallet.WalletCard>, int);
+ method public int describeContents();
+ method public int getSelectedIndex();
+ method @NonNull public java.util.List<android.service.quickaccesswallet.WalletCard> getWalletCards();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.GetWalletCardsResponse> CREATOR;
+ }
+
+ public abstract class QuickAccessWalletService extends android.app.Service {
+ ctor public QuickAccessWalletService();
+ method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onWalletCardSelected(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest);
+ method public abstract void onWalletCardsRequested(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.GetWalletCardsCallback);
+ method public abstract void onWalletDismissed();
+ method public final void sendWalletServiceEvent(@NonNull android.service.quickaccesswallet.WalletServiceEvent);
+ field public static final String ACTION_DISMISS_WALLET = "android.service.quickaccesswallet.action.DISMISS_WALLET";
+ field public static final String ACTION_VIEW_WALLET = "android.service.quickaccesswallet.action.VIEW_WALLET";
+ field public static final String ACTION_VIEW_WALLET_SETTINGS = "android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS";
+ field public static final String SERVICE_INTERFACE = "android.service.quickaccesswallet.QuickAccessWalletService";
+ field public static final String SERVICE_META_DATA = "android.quickaccesswallet";
+ }
+
+ public final class SelectWalletCardRequest implements android.os.Parcelable {
+ ctor public SelectWalletCardRequest(@NonNull String);
+ method public int describeContents();
+ method @NonNull public String getCardId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.SelectWalletCardRequest> CREATOR;
+ }
+
+ public final class WalletCard implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.graphics.drawable.Icon getCardIcon();
+ method @NonNull public String getCardId();
+ method @NonNull public android.graphics.drawable.Icon getCardImage();
+ method @Nullable public CharSequence getCardLabel();
+ method @NonNull public CharSequence getContentDescription();
+ method @NonNull public android.app.PendingIntent getPendingIntent();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.WalletCard> CREATOR;
+ }
+
+ public static final class WalletCard.Builder {
+ ctor public WalletCard.Builder(@NonNull String, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence, @NonNull android.app.PendingIntent);
+ method @NonNull public android.service.quickaccesswallet.WalletCard build();
+ method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardLabel(@Nullable CharSequence);
+ }
+
+ public final class WalletServiceEvent implements android.os.Parcelable {
+ ctor public WalletServiceEvent(int);
+ method public int describeContents();
+ method public int getEventType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.WalletServiceEvent> CREATOR;
+ field public static final int TYPE_NFC_PAYMENT_STARTED = 1; // 0x1
+ }
+
+}
+
package android.service.quicksettings {
public final class Tile implements android.os.Parcelable {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b97482a..7ebe7f1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1969,6 +1969,21 @@
"android.settings.REQUEST_SET_AUTOFILL_SERVICE";
/**
+ * Activity Action: Show screen for controlling the Quick Access Wallet.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: The Intent's data URI specifies the application package name
+ * to be shown, with the "package" scheme. That is "package:com.my.app".
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_QUICK_ACCESS_WALLET_SETTINGS =
+ "android.settings.QUICK_ACCESS_WALLET_SETTINGS";
+
+ /**
* Activity Action: Show screen for controlling which apps have access on volume directories.
* <p>
* Input: Nothing.
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java b/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java
new file mode 100644
index 0000000..9d210cd
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsCallback.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Handles response from the {@link QuickAccessWalletService} for {@link GetWalletCardsRequest}
+ */
+public final class GetWalletCardsCallback {
+
+ private static final String TAG = "QAWalletCallback";
+
+ private final IQuickAccessWalletServiceCallbacks mCallback;
+ private final Handler mHandler;
+ private boolean mCalled;
+
+ /**
+ * @hide
+ */
+ GetWalletCardsCallback(IQuickAccessWalletServiceCallbacks callback, Handler handler) {
+ mCallback = callback;
+ mHandler = handler;
+ }
+
+ /**
+ * Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
+ * was successfully handled by the service.
+ *
+ * @param response The response contains the list of {@link WalletCard walletCards} to be shown
+ * to the user as well as the index of the card that should initially be
+ * presented as the selected card.
+ */
+ public void onSuccess(@NonNull GetWalletCardsResponse response) {
+ mHandler.post(() -> onSuccessInternal(response));
+ }
+
+ /**
+ * Notifies the Android System that an {@link QuickAccessWalletService#onWalletCardsRequested}
+ * could not be handled by the service.
+ *
+ * @param error The error message. <b>Note: </b> this message should <b>not</b> contain PII
+ * (Personally Identifiable Information, such as username or email address).
+ * @throws IllegalStateException if this method or {@link #onSuccess} was already called.
+ */
+ public void onFailure(@NonNull GetWalletCardsError error) {
+ mHandler.post(() -> onFailureInternal(error));
+ }
+
+ private void onSuccessInternal(GetWalletCardsResponse response) {
+ if (mCalled) {
+ Log.w(TAG, "already called");
+ return;
+ }
+ mCalled = true;
+ try {
+ mCallback.onGetWalletCardsSuccess(response);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error returning wallet cards", e);
+ }
+ }
+
+ private void onFailureInternal(GetWalletCardsError error) {
+ if (mCalled) {
+ Log.w(TAG, "already called");
+ return;
+ }
+ mCalled = true;
+ try {
+ mCallback.onGetWalletCardsFailure(error);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error returning failure message", e);
+ }
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsError.aidl b/core/java/android/service/quickaccesswallet/GetWalletCardsError.aidl
new file mode 100644
index 0000000..847f5ac
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsError.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+parcelable GetWalletCardsError;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsError.java b/core/java/android/service/quickaccesswallet/GetWalletCardsError.java
new file mode 100644
index 0000000..527d2b7
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsError.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Error response for an {@link GetWalletCardsRequest}.
+ */
+public final class GetWalletCardsError implements Parcelable {
+
+ private final Icon mIcon;
+ private final CharSequence mMessage;
+
+ /**
+ * Construct a new error response. If provided, the icon and message will be displayed to the
+ * user.
+ *
+ * @param icon an icon to be shown to the user next to the message. Optional.
+ * @param message message to be shown to the user. Optional.
+ */
+ public GetWalletCardsError(@Nullable Icon icon, @Nullable CharSequence message) {
+ mIcon = icon;
+ mMessage = message;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ if (mIcon == null) {
+ dest.writeByte((byte) 0);
+ } else {
+ dest.writeByte((byte) 1);
+ mIcon.writeToParcel(dest, flags);
+ }
+ TextUtils.writeToParcel(mMessage, dest, flags);
+ }
+
+ private static GetWalletCardsError readFromParcel(Parcel source) {
+ Icon icon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
+ CharSequence message = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ return new GetWalletCardsError(icon, message);
+ }
+
+ @NonNull
+ public static final Creator<GetWalletCardsError> CREATOR =
+ new Creator<GetWalletCardsError>() {
+ @Override
+ public GetWalletCardsError createFromParcel(Parcel source) {
+ return readFromParcel(source);
+ }
+
+ @Override
+ public GetWalletCardsError[] newArray(int size) {
+ return new GetWalletCardsError[size];
+ }
+ };
+
+ /**
+ * An icon that may be displayed with the message to provide a visual indication of why cards
+ * could not be provided in the Quick Access Wallet.
+ */
+ @Nullable
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * A localized message that may be shown to the user in the event that the wallet cards cannot
+ * be retrieved. <b>Note: </b> this message should <b>not</b> contain PII (Personally
+ * Identifiable Information, such as username or email address).
+ */
+ @Nullable
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.aidl b/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.aidl
new file mode 100644
index 0000000..e70a982
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+parcelable GetWalletCardsRequest;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.java b/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.java
new file mode 100644
index 0000000..2ba448f
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsRequest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a request to a {@link QuickAccessWalletService} for {@link WalletCard walletCards}.
+ * Wallet cards may represent anything that a user might carry in their wallet -- a credit card,
+ * library card, a transit pass, etc. This request contains the desired size of the card images and
+ * icons as well as the maximum number of cards that may be returned in the {@link
+ * GetWalletCardsResponse}.
+ *
+ * <p>Cards may be displayed with an optional icon and label. The icon and label should communicate
+ * the same idea. For example, if a card can be used at an NFC terminal, the icon could be an NFC
+ * icon and the label could inform the user how to interact with the NFC terminal.
+ *
+ * <p>The maximum number of cards that may be displayed in the wallet is provided in {@link
+ * #getMaxCards()}. The {@link QuickAccessWalletService} may provide up to this many cards in the
+ * {@link GetWalletCardsResponse#getWalletCards()}. If the list of cards provided exceeds this
+ * number, some of the cards may not be shown to the user.
+ */
+public final class GetWalletCardsRequest implements Parcelable {
+
+ private final int mCardWidthPx;
+ private final int mCardHeightPx;
+ private final int mIconSizePx;
+ private final int mMaxCards;
+
+ /**
+ * Creates a new GetWalletCardsRequest.
+ *
+ * @param cardWidthPx The width of the card image in pixels.
+ * @param cardHeightPx The height of the card image in pixels.
+ * @param iconSizePx The width and height of the optional card icon in pixels.
+ * @param maxCards The maximum number of cards that may be provided in the response.
+ */
+ public GetWalletCardsRequest(int cardWidthPx, int cardHeightPx, int iconSizePx, int maxCards) {
+ this.mCardWidthPx = cardWidthPx;
+ this.mCardHeightPx = cardHeightPx;
+ this.mIconSizePx = iconSizePx;
+ this.mMaxCards = maxCards;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mCardWidthPx);
+ dest.writeInt(mCardHeightPx);
+ dest.writeInt(mIconSizePx);
+ dest.writeInt(mMaxCards);
+ }
+
+ @NonNull
+ public static final Creator<GetWalletCardsRequest> CREATOR =
+ new Creator<GetWalletCardsRequest>() {
+ @Override
+ public GetWalletCardsRequest createFromParcel(Parcel source) {
+ int cardWidthPx = source.readInt();
+ int cardHeightPx = source.readInt();
+ int iconSizePx = source.readInt();
+ int maxCards = source.readInt();
+ return new GetWalletCardsRequest(cardWidthPx,
+ cardHeightPx,
+ iconSizePx,
+ maxCards);
+ }
+
+ @Override
+ public GetWalletCardsRequest[] newArray(int size) {
+ return new GetWalletCardsRequest[size];
+ }
+ };
+
+ /**
+ * The desired width of the {@link WalletCard#getCardImage()}, in pixels. The dimensions of the
+ * card image are requested so that it may be rendered without scaling.
+ * <p>
+ * The {@code cardWidthPx} and {@code cardHeightPx} should be applied to the size of the {@link
+ * WalletCard#getCardImage()}. The size of the card image is specified so that it may be
+ * rendered accurately and without distortion caused by scaling.
+ */
+ public int getCardWidthPx() {
+ return mCardWidthPx;
+ }
+
+ /**
+ * The desired height of the {@link WalletCard#getCardImage()}, in pixels. The dimensions of the
+ * card image are requested so that it may be rendered without scaling.
+ */
+ public int getCardHeightPx() {
+ return mCardHeightPx;
+ }
+
+ /**
+ * Wallet cards may be displayed next to an icon. The icon can help to convey additional
+ * information about the state of the card. If the provided icon is a bitmap, its width and
+ * height should equal iconSizePx so that it is rendered without distortion caused by scaling.
+ */
+ public int getIconSizePx() {
+ return mIconSizePx;
+ }
+
+ /**
+ * The maximum size of the {@link GetWalletCardsResponse#getWalletCards()}. If the list of cards
+ * exceeds this number, not all cards may be displayed.
+ */
+ public int getMaxCards() {
+ return mMaxCards;
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.aidl b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.aidl
new file mode 100644
index 0000000..b0f25b3
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+parcelable GetWalletCardsResponse;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
new file mode 100644
index 0000000..996622a
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The response for an {@link GetWalletCardsRequest} contains a list of wallet cards and the index
+ * of the card that should initially be displayed in the 'selected' position.
+ */
+public final class GetWalletCardsResponse implements Parcelable {
+
+ private final List<WalletCard> mWalletCards;
+ private final int mSelectedIndex;
+
+ /**
+ * Construct a new response.
+ *
+ * @param walletCards The list of wallet cards.
+ * @param selectedIndex The index of the card that should be presented as the initially
+ * 'selected' card
+ */
+ public GetWalletCardsResponse(@NonNull List<WalletCard> walletCards, int selectedIndex) {
+ this.mWalletCards = walletCards;
+ this.mSelectedIndex = selectedIndex;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mWalletCards.size());
+ dest.writeParcelableList(mWalletCards, flags);
+ dest.writeInt(mSelectedIndex);
+ }
+
+ private static GetWalletCardsResponse readFromParcel(Parcel source) {
+ int size = source.readInt();
+ List<WalletCard> walletCards =
+ source.readParcelableList(new ArrayList<>(size), WalletCard.class.getClassLoader());
+ int selectedIndex = source.readInt();
+ return new GetWalletCardsResponse(walletCards, selectedIndex);
+ }
+
+ @NonNull
+ public static final Creator<GetWalletCardsResponse> CREATOR =
+ new Creator<GetWalletCardsResponse>() {
+ @Override
+ public GetWalletCardsResponse createFromParcel(Parcel source) {
+ return readFromParcel(source);
+ }
+
+ @Override
+ public GetWalletCardsResponse[] newArray(int size) {
+ return new GetWalletCardsResponse[size];
+ }
+ };
+
+ /**
+ * The list of {@link WalletCard}s. The size of this list should not exceed {@link
+ * GetWalletCardsRequest#getMaxCards()}.
+ */
+ @NonNull
+ public List<WalletCard> getWalletCards() {
+ return mWalletCards;
+ }
+
+ /**
+ * The {@code selectedIndex} represents the index of the card that should be presented in the
+ * 'selected' position when the cards are initially displayed in the quick access wallet. The
+ * {@code selectedIndex} should be greater than or equal to zero and less than the size of the
+ * list of {@link WalletCard walletCards}, unless the list is empty in which case the {@code
+ * selectedIndex} can take any value. 0 is a nice round number for such cases.
+ */
+ public int getSelectedIndex() {
+ return mSelectedIndex;
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
new file mode 100644
index 0000000..ee70be4
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.service.quickaccesswallet.GetWalletCardsRequest;
+import android.service.quickaccesswallet.IQuickAccessWalletServiceCallbacks;
+import android.service.quickaccesswallet.SelectWalletCardRequest;
+import android.service.quickaccesswallet.WalletServiceEvent;
+import android.service.quickaccesswallet.WalletServiceEventListenerRequest;
+
+/**
+ * Implemented by QuickAccessWalletService in the payment application
+ *
+ * @hide
+ */
+interface IQuickAccessWalletService {
+ // Request to get cards, which should be provided using the callback.
+ oneway void onWalletCardsRequested(
+ in GetWalletCardsRequest request, in IQuickAccessWalletServiceCallbacks callback);
+ // Indicates that a card has been selected.
+ oneway void onWalletCardSelected(in SelectWalletCardRequest request);
+ // Sent when the wallet is dismissed or closed.
+ oneway void onWalletDismissed();
+ // Register an event listener
+ oneway void registerWalletServiceEventListener(
+ in WalletServiceEventListenerRequest request,
+ in IQuickAccessWalletServiceCallbacks callback);
+ // Unregister an event listener
+ oneway void unregisterWalletServiceEventListener(in WalletServiceEventListenerRequest request);
+}
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
new file mode 100644
index 0000000..f37b930
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.service.quickaccesswallet.GetWalletCardsError;
+import android.service.quickaccesswallet.GetWalletCardsResponse;
+import android.service.quickaccesswallet.WalletServiceEvent;
+
+/**
+ * Interface to receive the result of requests to the wallet application.
+ *
+ * @hide
+ */
+interface IQuickAccessWalletServiceCallbacks {
+ // Called in response to onWalletCardsRequested on success. May only be called once per request.
+ oneway void onGetWalletCardsSuccess(in GetWalletCardsResponse response);
+ // Called in response to onWalletCardsRequested when an error occurs. May only be called once
+ // per request.
+ oneway void onGetWalletCardsFailure(in GetWalletCardsError error);
+ // Called in response to registerWalletServiceEventListener. May be called multiple times as
+ // long as the event listener is registered.
+ oneway void onWalletServiceEvent(in WalletServiceEvent event);
+}
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
new file mode 100644
index 0000000..cfc6d57
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.function.Consumer;
+
+/**
+ * Facilitates accessing cards from the {@link QuickAccessWalletService}.
+ *
+ * @hide
+ */
+public interface QuickAccessWalletClient {
+
+ /**
+ * Create a client for accessing wallet cards from the {@link QuickAccessWalletService}. If the
+ * service is unavailable, {@link #isWalletServiceAvailable()} will return false.
+ */
+ @NonNull
+ static QuickAccessWalletClient create(@NonNull Context context) {
+ return new QuickAccessWalletClientImpl(context);
+ }
+
+ /**
+ * @return true if the {@link QuickAccessWalletService} is available.
+ */
+ boolean isWalletServiceAvailable();
+
+ /**
+ * Get wallet cards from the {@link QuickAccessWalletService}.
+ */
+ void getWalletCards(
+ @NonNull GetWalletCardsRequest request,
+ @NonNull Consumer<GetWalletCardsResponse> onSuccessListener,
+ @NonNull Consumer<GetWalletCardsError> onFailureListener);
+
+ /**
+ * Notify the {@link QuickAccessWalletService} service that a wallet card was selected.
+ */
+ void selectWalletCard(@NonNull SelectWalletCardRequest request);
+
+ /**
+ * Notify the {@link QuickAccessWalletService} service that the Wallet was dismissed.
+ */
+ void notifyWalletDismissed();
+
+ /**
+ * Unregister event listener.
+ */
+ void registerWalletServiceEventListener(Consumer<WalletServiceEvent> listener);
+
+ /**
+ * Unregister event listener
+ */
+ void unregisterWalletServiceEventListener(Consumer<WalletServiceEvent> listener);
+
+ /**
+ * The manifest entry for the QuickAccessWalletService may also publish information about the
+ * activity that hosts the Wallet view. This is typically the home screen of the Wallet
+ * application.
+ */
+ @Nullable
+ Intent getWalletActivity();
+
+ /**
+ * The manifest entry for the {@link QuickAccessWalletService} may publish the activity that
+ * hosts the settings
+ */
+ @Nullable
+ Intent getSettingsActivity();
+}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
new file mode 100644
index 0000000..17c287f
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import static android.service.quickaccesswallet.QuickAccessWalletService.SERVICE_INTERFACE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+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.text.TextUtils;
+import android.util.Log;
+
+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;
+
+/**
+ * @hide
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Handler.Callback,
+ ServiceConnection {
+
+ private static final String TAG = "QAWalletSClient";
+ private final Handler mHandler;
+ private final Context mContext;
+ private final Queue<ApiCaller> mRequestQueue;
+ private final Map<Consumer<WalletServiceEvent>, String> mEventListeners;
+ private boolean mIsConnected;
+ @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;
+
+ QuickAccessWalletClientImpl(@NonNull Context context) {
+ mContext = context.getApplicationContext();
+ mServiceInfo = QuickAccessWalletServiceInfo.tryCreate(context);
+ mHandler = new Handler(Looper.getMainLooper(), this);
+ 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;
+ }
+ return true;
+ }
+
+ private void connect() {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECT));
+ }
+
+ private void connectInternal() {
+ if (mServiceInfo == null) {
+ Log.w(TAG, "Wallet service unavailable");
+ return;
+ }
+ if (mIsConnected) {
+ Log.w(TAG, "already connected");
+ return;
+ }
+ mIsConnected = true;
+ Intent intent = new Intent(SERVICE_INTERFACE);
+ intent.setComponent(mServiceInfo.getComponentName());
+ int flags = Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY;
+ mContext.bindService(intent, this, flags);
+ }
+
+ private void onConnectedInternal(IQuickAccessWalletService service) {
+ if (!mIsConnected) {
+ Log.w(TAG, "onConnectInternal but connection closed");
+ mService = null;
+ return;
+ }
+ mService = service;
+ for (ApiCaller apiCaller : new ArrayList<>(mRequestQueue)) {
+ try {
+ apiCaller.performApiCall(mService);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onConnectedInternal error", e);
+ apiCaller.onApiError();
+ disconnect();
+ break;
+ }
+ mRequestQueue.remove(apiCaller);
+ }
+ }
+
+ private void disconnect() {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DISCONNECT));
+ }
+
+ private void disconnectInternal() {
+ if (!mIsConnected) {
+ Log.w(TAG, "already disconnected");
+ return;
+ }
+ mIsConnected = false;
+ mContext.unbindService(/*conn=*/this);
+ mService = null;
+ mEventListeners.clear();
+ mRequestQueue.clear();
+ }
+
+ private void execute(ApiCaller apiCaller) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_EXECUTE, apiCaller));
+ }
+
+ private void executeInternal(ApiCaller apiCall) {
+ if (mIsConnected && mService != null) {
+ try {
+ apiCall.performApiCall(mService);
+ } catch (RemoteException e) {
+ Log.w(TAG, "executeInternal error", e);
+ apiCall.onApiError();
+ disconnect();
+ }
+ } else {
+ mRequestQueue.add(apiCall);
+ connect();
+ }
+ }
+
+ public boolean isWalletServiceAvailable() {
+ return mServiceInfo != null;
+ }
+
+ private abstract static class ApiCaller {
+ abstract void performApiCall(IQuickAccessWalletService service) throws RemoteException;
+
+ void onApiError() {
+ Log.w(TAG, "api error");
+ }
+ }
+
+ public void getWalletCards(
+ @NonNull GetWalletCardsRequest request,
+ @NonNull Consumer<GetWalletCardsResponse> onSuccessListener,
+ @NonNull Consumer<GetWalletCardsError> 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<WalletServiceEvent> 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<WalletServiceEvent> 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
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ IQuickAccessWalletService service = IQuickAccessWalletService.Stub.asInterface(binder);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTED, service));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // Do not disconnect, as we may later be re-connected
+ Log.w(TAG, "onServiceDisconnected");
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ // This is a recoverable error but the client will need to reconnect.
+ Log.w(TAG, "onBindingDied");
+ disconnect();
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ Log.w(TAG, "onNullBinding");
+ disconnect();
+ }
+
+ private static class BaseCallbacks extends IQuickAccessWalletServiceCallbacks.Stub {
+ public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
+ throw new IllegalStateException();
+ }
+
+ public void onGetWalletCardsFailure(GetWalletCardsError error) {
+ throw new IllegalStateException();
+ }
+
+ public void onWalletServiceEvent(WalletServiceEvent event) {
+ throw new IllegalStateException();
+ }
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
new file mode 100644
index 0000000..d968405
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+
+/**
+ * A {@code QuickAccessWalletService} provides a list of {@code WalletCard}s shown in the Quick
+ * Access Wallet. The Quick Access Wallet allows the user to change their selected payment method
+ * and access other important passes, such as tickets and transit passes, without leaving the
+ * context of their current app.
+ *
+ * <p>An {@code QuickAccessWalletService} is only bound to the Android System for the purposes of
+ * showing wallet cards if:
+ * <ol>
+ * <li>The application hosting the QuickAccessWalletService is also the default NFC payment
+ * application. This means that the same application must also have a
+ * {@link android.nfc.cardemulation.HostApduService} or
+ * {@link android.nfc.cardemulation.OffHostApduService} that requires the
+ * android.permission.BIND_NFC_SERVICE permission.
+ * <li>The user explicitly selected the application as the default payment application in
+ * the Tap & pay settings screen.
+ * <li>The application requires the {@code android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE}
+ * permission in its manifest.
+ * <li>The user explicitly enables it using Android Settings (the
+ * {@link Settings#ACTION_QUICK_ACCESS_WALLET_SETTINGS} intent can be used to launch it).
+ * </ol>
+ *
+ * <a name="BasicUsage"></a>
+ * <h3>Basic usage</h3>
+ *
+ * <p>The basic Quick Access Wallet process is defined by the workflow below:
+ * <ol>
+ * <li>User performs a gesture to bring up the Quick Access Wallet, which is displayed by the
+ * Android System.
+ * <li>The Android System creates a {@link GetWalletCardsRequest}, binds to the
+ * {@link QuickAccessWalletService}, and delivers the request.
+ * <li>The service receives the request through {@link #onWalletCardsRequested}
+ * <li>The service responds by calling {@link GetWalletCardsCallback#onSuccess} with a
+ * {@link GetWalletCardsResponse response} that contains between 1 and
+ * {@link GetWalletCardsRequest#getMaxCards() maxCards} cards.
+ * <li>The Android System displays the Quick Access Wallet containing the provided cards. The
+ * card at the {@link GetWalletCardsResponse#getSelectedIndex() selectedIndex} will initially
+ * be presented as the 'selected' card.
+ * <li>As soon as the cards are displayed, the Android System will notify the service that the
+ * card at the selected index has been selected through {@link #onWalletCardSelected}.
+ * <li>The user interacts with the wallet and may select one or more cards in sequence. Each time
+ * 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.
+ * <li>When the wallet is dismissed, the Android System will notify the service through
+ * {@link #onWalletDismissed}.
+ * </ol>
+ *
+ * <p>The workflow is designed to minimize the time that the Android System is bound to the
+ * service, but connections may be cached and reused to improve performance and conserve memory.
+ * All calls should be considered stateless: if the service needs to keep state between calls, it
+ * must do its own state management (keeping in mind that the service's process might be killed
+ * by the Android System when unbound; for example, if the device is running low in memory).
+ *
+ * <p>
+ * <a name="ErrorHandling"></a>
+ * <h3>Error handling</h3>
+ * <p>If the service encountered an error processing the request, it should call
+ * {@link GetWalletCardsCallback#onFailure}.
+ * For performance reasons, it's paramount that the service calls either
+ * {@link GetWalletCardsCallback#onSuccess} or
+ * {@link GetWalletCardsCallback#onFailure} for each
+ * {@link #onWalletCardsRequested} received - if it doesn't, the request will eventually time out
+ * and be discarded by the Android System.
+ *
+ * <p>
+ * <a name="ManifestEntry"></a>
+ * <h3>Manifest entry</h3>
+ *
+ * <p>QuickAccessWalletService must require the permission
+ * "android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE".
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <service
+ * android:name=".MyQuickAccessWalletService"
+ * android:label="@string/my_default_tile_label"
+ * android:icon="@drawable/my_default_icon_label"
+ * android:permission="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.service.quickaccesswallet.QuickAccessWalletService" />
+ * </intent-filter>
+ * <meta-data android:name="android.quickaccesswallet"
+ * android:resource="@xml/quickaccesswallet_configuration" />;
+ * </service>}
+ * </pre>
+ * <p>
+ * The {@literal <meta-data>} element includes an android:resource attribute that points to an
+ * XML resource with further details about the service. The {@code quickaccesswallet_configuration}
+ * in the example above specifies an activity that allows the users to view the entire wallet.
+ * The following example shows the quickaccesswallet_configuration XML resource:
+ * <p>
+ * <pre class="prettyprint">
+ * {@literal
+ * <quickaccesswallet-service
+ * xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:settingsActivity="com.example.android.SettingsActivity"
+ * android:targetActivity="com.example.android.WalletActivity"/>
+ * }
+ * </pre>
+ *
+ * <p>The entry for {@code settingsActivity} should contain the fully qualified class name of an
+ * activity that allows the user to modify the settings for this service. The {@code targetActivity}
+ * entry should contain the fully qualified class name of an activity that allows the user to view
+ * their entire wallet. If specified, the wallet activity will be started with the Intent action
+ * {@link #ACTION_VIEW_WALLET} and the settings activity will be started with the Intent action
+ * {@link #ACTION_VIEW_WALLET_SETTINGS}.
+ */
+public abstract class QuickAccessWalletService extends Service {
+
+ private static final String TAG = "QAWalletService";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the
+ * {@link android.Manifest.permission#BIND_QUICK_ACCESS_WALLET_SERVICE}
+ * permission so that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.quickaccesswallet.QuickAccessWalletService";
+
+ /**
+ * Intent action to launch an activity to display the wallet.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VIEW_WALLET =
+ "android.service.quickaccesswallet.action.VIEW_WALLET";
+
+ /**
+ * Intent action to launch an activity to display quick access wallet settings.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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}
+ * <p>
+ * <pre class="prettyprint">
+ * context.sendBroadcast(
+ * new Intent(ACTION_DISMISS_WALLET), Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE);
+ * </pre>
+ */
+ @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
+ * <code><{@link
+ * android.R.styleable#QuickAccessWalletService quickaccesswallet-service}></code> tag. This
+ * is a a sample XML file configuring an QuickAccessWalletService:
+ * <pre> <quickaccesswallet-service
+ * android:walletActivity="foo.bar.WalletActivity"
+ * . . .
+ * /></pre>
+ */
+ public static final String SERVICE_META_DATA = "android.quickaccesswallet";
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ @Nullable
+ private String mEventListenerId;
+ @Nullable
+ private IQuickAccessWalletServiceCallbacks mEventListener;
+
+ private final IQuickAccessWalletService mInterface = new IQuickAccessWalletService.Stub() {
+ @Override
+ public void onWalletCardsRequested(
+ @NonNull GetWalletCardsRequest request,
+ @NonNull IQuickAccessWalletServiceCallbacks callback) {
+ mHandler.post(() -> onWalletCardsRequestedInternal(request, callback));
+ }
+
+ @Override
+ public void onWalletCardSelected(@NonNull SelectWalletCardRequest request) {
+ mHandler.post(() -> QuickAccessWalletService.this.onWalletCardSelected(request));
+ }
+
+ @Override
+ public void onWalletDismissed() {
+ mHandler.post(QuickAccessWalletService.this::onWalletDismissed);
+ }
+
+ public void registerWalletServiceEventListener(
+ @NonNull WalletServiceEventListenerRequest request,
+ @NonNull IQuickAccessWalletServiceCallbacks callback) {
+ mHandler.post(() -> registerDismissWalletListenerInternal(request, callback));
+ }
+
+ public void unregisterWalletServiceEventListener(
+ @NonNull WalletServiceEventListenerRequest request) {
+ mHandler.post(() -> unregisterDismissWalletListenerInternal(request));
+ }
+ };
+
+ private void onWalletCardsRequestedInternal(
+ GetWalletCardsRequest request,
+ IQuickAccessWalletServiceCallbacks callback) {
+ onWalletCardsRequested(request, new GetWalletCardsCallback(callback, mHandler));
+ }
+
+ @Override
+ @Nullable
+ public IBinder onBind(@NonNull Intent intent) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ // 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.
+ Log.w(TAG, "Warning: binding on pre-R device");
+ }
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+ return null;
+ }
+
+ /**
+ * Called when the user requests the service to provide wallet cards.
+ *
+ * <p>This method will be called on the main thread, but the callback may be called from any
+ * thread. The callback should be called as quickly as possible. The service must always call
+ * either {@link GetWalletCardsCallback#onSuccess(GetWalletCardsResponse)} or {@link
+ * GetWalletCardsCallback#onFailure(GetWalletCardsError)}. Calling multiple times or calling
+ * both methods will cause an exception to be thrown.
+ */
+ public abstract void onWalletCardsRequested(
+ @NonNull GetWalletCardsRequest request,
+ @NonNull GetWalletCardsCallback callback);
+
+ /**
+ * A wallet card was selected. Sent when the user selects a wallet card from the list of cards.
+ * Selection may indicate that the card is now in the center of the screen, or highlighted in
+ * some other fashion. It does not mean that the user clicked on the card -- clicking on the
+ * card will cause the {@link WalletCard#getPendingIntent()} to be sent.
+ *
+ * <p>Card selection events are especially important to NFC payment applications because
+ * many NFC terminals can only accept one payment card at a time. If the user has several NFC
+ * cards in their wallet, selecting different cards can change which payment method is presented
+ * to the terminal.
+ */
+ public abstract void onWalletCardSelected(@NonNull SelectWalletCardRequest request);
+
+ /**
+ * Indicates that the wallet was dismissed. This is received when the Quick Access Wallet is no
+ * longer visible.
+ */
+ public abstract void onWalletDismissed();
+
+ /**
+ * Send a {@link WalletServiceEvent} to the Quick Access Wallet.
+ * <p>
+ * Background events may require that the Quick Access Wallet view be updated. For example, if
+ * the wallet application hosting this service starts to handle an NFC payment while the Quick
+ * Access Wallet is being shown, the Quick Access Wallet will need to be dismissed so that the
+ * Activity showing the payment can be displayed to the user.
+ */
+ public final void sendWalletServiceEvent(@NonNull WalletServiceEvent serviceEvent) {
+ mHandler.post(() -> sendWalletServiceEventInternal(serviceEvent));
+ }
+
+ private void sendWalletServiceEventInternal(WalletServiceEvent serviceEvent) {
+ if (mEventListener == null) {
+ Log.i(TAG, "No dismiss listener registered");
+ return;
+ }
+ try {
+ mEventListener.onWalletServiceEvent(serviceEvent);
+ } catch (RemoteException e) {
+ Log.w(TAG, "onWalletServiceEvent error", e);
+ mEventListenerId = null;
+ mEventListener = null;
+ }
+ }
+
+ private void registerDismissWalletListenerInternal(
+ @NonNull WalletServiceEventListenerRequest request,
+ @NonNull IQuickAccessWalletServiceCallbacks callback) {
+ mEventListenerId = request.getListenerId();
+ mEventListener = callback;
+ }
+
+ private void unregisterDismissWalletListenerInternal(
+ @NonNull WalletServiceEventListenerRequest request) {
+ if (mEventListenerId != null && mEventListenerId.equals(request.getListenerId())) {
+ mEventListenerId = null;
+ mEventListener = null;
+ } else {
+ Log.w(TAG, "dismiss listener missing or replaced");
+ }
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
new file mode 100644
index 0000000..8793f28
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * {@link ServiceInfo} and meta-data about a {@link QuickAccessWalletService}.
+ *
+ * @hide
+ */
+class QuickAccessWalletServiceInfo {
+
+ private static final String TAG = "QAWalletSInfo";
+ private static final String TAG_WALLET_SERVICE = "quickaccesswallet-service";
+
+ private final ServiceInfo mServiceInfo;
+ private final ServiceMetadata mServiceMetadata;
+
+ private QuickAccessWalletServiceInfo(
+ @NonNull ServiceInfo serviceInfo,
+ @NonNull ServiceMetadata metadata) {
+ mServiceInfo = serviceInfo;
+ mServiceMetadata = metadata;
+ }
+
+ @Nullable
+ static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
+ ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
+ if (defaultPaymentApp == null) {
+ Log.d(TAG, "create: default payment app not set");
+ return null;
+ }
+
+ ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultPaymentApp.getPackageName());
+ if (serviceInfo == null) {
+ Log.d(TAG, "create: unable to resolve service intent");
+ return null;
+ }
+
+ 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));
+ return null;
+ }
+
+ ServiceMetadata metadata = parseServiceMetadata(context, serviceInfo);
+ return new QuickAccessWalletServiceInfo(serviceInfo, metadata);
+ }
+
+ private static ComponentName getDefaultPaymentApp(Context context) {
+ ContentResolver cr = context.getContentResolver();
+ String comp = Settings.Secure.getString(cr, Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
+ return comp == null ? null : ComponentName.unflattenFromString(comp);
+ }
+
+ private static ServiceInfo getWalletServiceInfo(Context context, String packageName) {
+ Intent intent = new Intent(QuickAccessWalletService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ List<ResolveInfo> resolveInfos =
+ context.getPackageManager().queryIntentServices(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ return resolveInfos.isEmpty() ? null : resolveInfos.get(0).serviceInfo;
+ }
+
+ private static class ServiceMetadata {
+ @Nullable
+ private final String mSettingsActivity;
+ @Nullable
+ private final String mWalletActivity;
+
+ private ServiceMetadata(String settingsActivity, String walletActivity) {
+ this.mSettingsActivity = settingsActivity;
+ this.mWalletActivity = walletActivity;
+ }
+ }
+
+ private static ServiceMetadata parseServiceMetadata(Context context, ServiceInfo serviceInfo) {
+ PackageManager pm = context.getPackageManager();
+ final XmlResourceParser parser =
+ serviceInfo.loadXmlMetaData(pm, QuickAccessWalletService.SERVICE_META_DATA);
+
+ if (parser == null) {
+ return new ServiceMetadata(null, null);
+ }
+
+ try {
+ Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
+ int type = 0;
+ while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+ type = parser.next();
+ }
+
+ if (TAG_WALLET_SERVICE.equals(parser.getName())) {
+ final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+ TypedArray afsAttributes = null;
+ try {
+ afsAttributes = resources.obtainAttributes(allAttributes,
+ R.styleable.QuickAccessWalletService);
+ String settingsActivity = afsAttributes.getString(
+ R.styleable.QuickAccessWalletService_settingsActivity);
+ String walletActivity = afsAttributes.getString(
+ R.styleable.QuickAccessWalletService_targetActivity);
+ return new ServiceMetadata(settingsActivity, walletActivity);
+ } finally {
+ if (afsAttributes != null) {
+ afsAttributes.recycle();
+ }
+ }
+ } else {
+ Log.e(TAG, "Meta-data does not start with quickaccesswallet-service tag");
+ }
+
+ } catch (PackageManager.NameNotFoundException
+ | IOException
+ | XmlPullParserException e) {
+ Log.e(TAG, "Error parsing quickaccesswallet service meta-data", e);
+ }
+ return new ServiceMetadata(null, null);
+ }
+
+ /**
+ * @return the component name of the {@link QuickAccessWalletService}
+ */
+ @NonNull
+ ComponentName getComponentName() {
+ return mServiceInfo.getComponentName();
+ }
+
+ /**
+ * @return the fully qualified name of the activity that hosts the full wallet. If available,
+ * this intent should be started with the action
+ * {@link QuickAccessWalletService#ACTION_VIEW_WALLET}
+ */
+ @Nullable
+ String getWalletActivity() {
+ return mServiceMetadata.mWalletActivity;
+ }
+
+ /**
+ * @return the fully qualified name of the activity that allows the user to change quick access
+ * wallet settings. If available, this intent should be started with the action {@link
+ * QuickAccessWalletService#ACTION_VIEW_WALLET_SETTINGS}
+ */
+ @Nullable
+ String getSettingsActivity() {
+ return mServiceMetadata.mSettingsActivity;
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.aidl b/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.aidl
new file mode 100644
index 0000000..97a0d41
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+parcelable SelectWalletCardRequest;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.java b/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.java
new file mode 100644
index 0000000..cb69eee
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/SelectWalletCardRequest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a request to a {@link QuickAccessWalletService} to select a particular {@link
+ * WalletCard walletCard}. Card selection events are transmitted to the WalletService so that the
+ * selected card may be used by the NFC payment service.
+ */
+public final class SelectWalletCardRequest implements Parcelable {
+
+ private final String mCardId;
+
+ /**
+ * Creates a new GetWalletCardsRequest.
+ *
+ * @param cardId The {@link WalletCard#getCardId() cardId} of the wallet card that is currently
+ * selected.
+ */
+ public SelectWalletCardRequest(@NonNull String cardId) {
+ this.mCardId = cardId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mCardId);
+ }
+
+ @NonNull
+ public static final Creator<SelectWalletCardRequest> CREATOR =
+ new Creator<SelectWalletCardRequest>() {
+ @Override
+ public SelectWalletCardRequest createFromParcel(Parcel source) {
+ String cardId = source.readString();
+ return new SelectWalletCardRequest(cardId);
+ }
+
+ @Override
+ public SelectWalletCardRequest[] newArray(int size) {
+ return new SelectWalletCardRequest[size];
+ }
+ };
+
+ /**
+ * The {@link WalletCard#getCardId() cardId} of the wallet card that is currently selected.
+ */
+ @NonNull
+ public String getCardId() {
+ return mCardId;
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.aidl b/core/java/android/service/quickaccesswallet/WalletCard.aidl
new file mode 100644
index 0000000..115213d
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletCard.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+parcelable WalletCard;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
new file mode 100644
index 0000000..c3b1a4b
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit
+ * card, library card, transit pass, etc. Cards are identified by a String identifier and contain a
+ * card image, card image content description, and a {@link PendingIntent} to be used if the user
+ * clicks on the card. Cards may be displayed with an icon and label, though these are optional.
+ */
+public final class WalletCard implements Parcelable {
+
+ private final String mCardId;
+ private final Icon mCardImage;
+ private final CharSequence mContentDescription;
+ private final PendingIntent mPendingIntent;
+ private final Icon mCardIcon;
+ private final CharSequence mCardLabel;
+
+ private WalletCard(Builder builder) {
+ this.mCardId = builder.mCardId;
+ this.mCardImage = builder.mCardImage;
+ this.mContentDescription = builder.mContentDescription;
+ this.mPendingIntent = builder.mPendingIntent;
+ this.mCardIcon = builder.mCardIcon;
+ this.mCardLabel = builder.mCardLabel;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mCardId);
+ mCardImage.writeToParcel(dest, flags);
+ TextUtils.writeToParcel(mContentDescription, dest, flags);
+ PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
+ if (mCardIcon == null) {
+ dest.writeByte((byte) 0);
+ } else {
+ dest.writeByte((byte) 1);
+ mCardIcon.writeToParcel(dest, flags);
+ }
+ TextUtils.writeToParcel(mCardLabel, dest, flags);
+ }
+
+ private static WalletCard readFromParcel(Parcel source) {
+ String cardId = source.readString();
+ Icon cardImage = Icon.CREATOR.createFromParcel(source);
+ CharSequence contentDesc = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source);
+ Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
+ CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ return new Builder(cardId, cardImage, contentDesc, pendingIntent)
+ .setCardIcon(cardIcon)
+ .setCardLabel(cardLabel)
+ .build();
+ }
+
+ @NonNull
+ public static final Creator<WalletCard> CREATOR =
+ new Creator<WalletCard>() {
+ @Override
+ public WalletCard createFromParcel(Parcel source) {
+ return readFromParcel(source);
+ }
+
+ @Override
+ public WalletCard[] newArray(int size) {
+ return new WalletCard[size];
+ }
+ };
+
+ /**
+ * The card id must be unique within the list of cards returned.
+ */
+ @NonNull
+ public String getCardId() {
+ return mCardId;
+ }
+
+ /**
+ * 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()}.
+ */
+ @NonNull
+ public Icon getCardImage() {
+ return mCardImage;
+ }
+
+ /**
+ * The content description of the card image.
+ */
+ @NonNull
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * 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.
+ */
+ @NonNull
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ /**
+ * An icon may be shown alongside the card image to convey information about how the card can be
+ * used, or if some other action must be taken before using the card. For example, an NFC logo
+ * could indicate that the card is NFC-enabled and will be provided to an NFC terminal if the
+ * phone is held in close proximity to the NFC reader.
+ *
+ * <p>If the supplied Icon is backed by a bitmap, it should have width and height
+ * {@link GetWalletCardsRequest#getIconSizePx()}.
+ */
+ @Nullable
+ public Icon getCardIcon() {
+ return mCardIcon;
+ }
+
+ /**
+ * A card label may be shown alongside the card image to convey information about how the card
+ * can be used, or if some other action must be taken before using the card. For example, an
+ * NFC-enabled card could be labeled "Hold near reader" to inform the user of how to use NFC
+ * cards when interacting with an NFC reader.
+ *
+ * <p>If the provided label is too long to fit on one line, it may be truncated and ellipsized.
+ */
+ @Nullable
+ public CharSequence getCardLabel() {
+ return mCardLabel;
+ }
+
+ /**
+ * Builder for {@link WalletCard} objects. You must to provide cardId, cardImage,
+ * contentDescription, and pendingIntent. If the card is opaque and should be shown with
+ * elevation, set hasShadow to true. cardIcon and cardLabel are optional.
+ */
+ public static final class Builder {
+ private String mCardId;
+ private Icon mCardImage;
+ private CharSequence mContentDescription;
+ private PendingIntent mPendingIntent;
+ private Icon mCardIcon;
+ private CharSequence mCardLabel;
+
+ /**
+ * @param cardId The card id must be non-null and unique within the list of
+ * cards returned. <b>Note:
+ * </b> this card ID should <b>not</b> contain PII (Personally
+ * 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.
+ * @param contentDescription The content description of the card image. This field is
+ * required.
+ * <b>Note: </b> this message should <b>not</b> 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.
+ */
+ public Builder(@NonNull String cardId,
+ @NonNull Icon cardImage,
+ @NonNull CharSequence contentDescription,
+ @NonNull PendingIntent pendingIntent) {
+ mCardId = cardId;
+ mCardImage = cardImage;
+ mContentDescription = contentDescription;
+ mPendingIntent = pendingIntent;
+ }
+
+ /**
+ * An icon may be shown alongside the card image to convey information about how the card
+ * can be used, or if some other action must be taken before using the card. For example, an
+ * NFC logo could indicate that the card is NFC-enabled and will be provided to an NFC
+ * terminal if the phone is held in close proximity to the NFC reader. This field is
+ * optional.
+ *
+ * <p>If the supplied Icon is backed by a bitmap, it should have width and height
+ * {@link GetWalletCardsRequest#getIconSizePx()}.
+ */
+ @NonNull
+ public Builder setCardIcon(@Nullable Icon cardIcon) {
+ mCardIcon = cardIcon;
+ return this;
+ }
+
+ /**
+ * A card label may be shown alongside the card image to convey information about how the
+ * card can be used, or if some other action must be taken before using the card. For
+ * example, an NFC-enabled card could be labeled "Hold near reader" to inform the user of
+ * how to use NFC cards when interacting with an NFC reader. This field is optional.
+ * <b>Note: </b> this card label should <b>not</b> contain PII (Personally Identifiable
+ * Information, such as username or email address). If the provided label is too long to fit
+ * on one line, it may be truncated and ellipsized.
+ */
+ @NonNull
+ public Builder setCardLabel(@Nullable CharSequence cardLabel) {
+ mCardLabel = cardLabel;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link WalletCard} instance.
+ *
+ * @return A built response.
+ */
+ @NonNull
+ public WalletCard build() {
+ return new WalletCard(this);
+ }
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/WalletServiceEvent.aidl b/core/java/android/service/quickaccesswallet/WalletServiceEvent.aidl
new file mode 100644
index 0000000..891cf1d
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletServiceEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+parcelable WalletServiceEvent;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/WalletServiceEvent.java b/core/java/android/service/quickaccesswallet/WalletServiceEvent.java
new file mode 100644
index 0000000..fb524be
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletServiceEvent.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents a request from the {@link QuickAccessWalletService wallet app} to the Quick Access
+ * Wallet in System UI. Background events may necessitate that the Quick Access Wallet update its
+ * view. For example, if the wallet application handles an NFC payment while the Quick Access Wallet
+ * is being shown, it needs to tell the Quick Access Wallet so that the wallet can be dismissed and
+ * Activity showing the payment can be displayed to the user.
+ */
+public final class WalletServiceEvent implements Parcelable {
+
+ /**
+ * An NFC payment has started. If the Quick Access Wallet is in a system window, it will need to
+ * be dismissed so that an Activity showing the payment can be displayed.
+ */
+ public static final int TYPE_NFC_PAYMENT_STARTED = 1;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TYPE_NFC_PAYMENT_STARTED})
+ public @interface EventType {
+ }
+
+ @EventType
+ private final int mEventType;
+
+ /**
+ * Creates a new DismissWalletRequest.
+ */
+ public WalletServiceEvent(@EventType int eventType) {
+ this.mEventType = eventType;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mEventType);
+ }
+
+ @NonNull
+ public static final Creator<WalletServiceEvent> CREATOR =
+ new Creator<WalletServiceEvent>() {
+ @Override
+ public WalletServiceEvent createFromParcel(Parcel source) {
+ int eventType = source.readInt();
+ return new WalletServiceEvent(eventType);
+ }
+
+ @Override
+ public WalletServiceEvent[] newArray(int size) {
+ return new WalletServiceEvent[size];
+ }
+ };
+
+ /**
+ * @return the event type
+ */
+ @EventType
+ public int getEventType() {
+ return mEventType;
+ }
+}
diff --git a/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.aidl b/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.aidl
new file mode 100644
index 0000000..155f92e
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+parcelable WalletServiceEventListenerRequest;
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.java b/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.java
new file mode 100644
index 0000000..223110e
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/WalletServiceEventListenerRequest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Register a dismiss request listener with the QuickAccessWalletService. This allows the service to
+ * dismiss the wallet if it needs to show a payment activity in response to an NFC event.
+ *
+ * @hide
+ */
+public final class WalletServiceEventListenerRequest implements Parcelable {
+
+ private final String mListenerId;
+
+ /**
+ * Construct a new {@code DismissWalletListenerRequest}.
+ *
+ * @param listenerKey A unique key that identifies the listener.
+ */
+ public WalletServiceEventListenerRequest(@NonNull String listenerKey) {
+ mListenerId = listenerKey;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mListenerId);
+ }
+
+ private static WalletServiceEventListenerRequest readFromParcel(Parcel source) {
+ String listenerId = source.readString();
+ return new WalletServiceEventListenerRequest(listenerId);
+ }
+
+ @NonNull
+ public static final Creator<WalletServiceEventListenerRequest> CREATOR =
+ new Creator<WalletServiceEventListenerRequest>() {
+ @Override
+ public WalletServiceEventListenerRequest createFromParcel(Parcel source) {
+ return readFromParcel(source);
+ }
+
+ @Override
+ public WalletServiceEventListenerRequest[] newArray(int size) {
+ return new WalletServiceEventListenerRequest[size];
+ }
+ };
+
+ /**
+ * Returns the unique key that identifies the wallet dismiss request listener.
+ */
+ @NonNull
+ public String getListenerId() {
+ return mListenerId;
+ }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 490c477..9232a91 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3226,6 +3226,13 @@
<permission android:name="android.permission.BIND_NFC_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by a {@link android.service.quickaccesswallet.QuickAccessWalletService}
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by the PrintSpooler to ensure that only the system can bind to it.
@hide -->
<permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e0d8492..940e9f1 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8329,6 +8329,26 @@
<attr name="successColor" format="color|reference"/>
</declare-styleable>
+ <!-- =============================== -->
+ <!-- QuickAccessWallet attributes -->
+ <!-- =============================== -->
+ <eat-comment />
+
+ <!-- Use <code>quickaccesswallet-service</code> as the root tag of the XML resource
+ that describes a {@link android.service.quickaccesswallet.QuickAccessWalletService},
+ which is referenced from its
+ {@link android.service.quickaccesswallet.QuickAccessWalletService#SERVICE_META_DATA}
+ meta-data entry.
+ -->
+ <declare-styleable name="QuickAccessWalletService">
+ <!-- Fully qualified class name of an activity that allows the user to modify
+ the settings for this service. -->
+ <attr name="settingsActivity"/>
+ <!-- Fully qualified class name of an activity that allows the user to view
+ their entire wallet -->
+ <attr name="targetActivity"/>
+ </declare-styleable>
+
<!-- Use <code>recognition-service</code> as the root tag of the XML resource that
describes a {@link android.speech.RecognitionService}, which is referenced from
its {@link android.speech.RecognitionService#SERVICE_META_DATA} meta-data entry.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0bcadce..6aeb0a1 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -174,6 +174,9 @@
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
+ <!-- Access Quick Access Wallet cards -->
+ <uses-permission android:name="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE" />
+
<!-- Adding Controls to SystemUI -->
<uses-permission android:name="android.permission.BIND_CONTROLS" />