diff options
26 files changed, 2222 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt index d8fac42e93f2..c1bb19e1ddde 100644 --- a/api/current.txt +++ b/api/current.txt @@ -39,6 +39,7 @@ package android { 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 @@ package android.provider { 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.notification { } +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 b97482a30cc0..7ebe7f1f974a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1969,6 +1969,21 @@ public final class Settings { "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 000000000000..9d210cd82c23 --- /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 000000000000..847f5accbd8e --- /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 000000000000..527d2b79b2de --- /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 000000000000..e70a98258afb --- /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 000000000000..2ba448fc03c4 --- /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 000000000000..b0f25b3c7d72 --- /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 000000000000..996622a3e5aa --- /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 000000000000..ee70be442d16 --- /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 000000000000..f37b930aabce --- /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 000000000000..cfc6d5777e82 --- /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 000000000000..17c287fa8eeb --- /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 000000000000..d968405002e3 --- /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 000000000000..8793f28bc708 --- /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 000000000000..97a0d41ff635 --- /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 000000000000..cb69eee03427 --- /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 000000000000..115213da2752 --- /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 000000000000..c3b1a4b0b85f --- /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 000000000000..891cf1d585bf --- /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 000000000000..fb524be852fa --- /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 000000000000..155f92e8acc6 --- /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 000000000000..223110e45d01 --- /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 490c47796472..9232a917869f 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 e0d849223552..940e9f1ef88b 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 0bcadce7a9c6..6aeb0a159bea 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" /> |