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
This commit is contained in:
Sean Pont
2020-01-07 12:05:09 -08:00
parent 30b7a57597
commit d00aefb43c
26 changed files with 2222 additions and 0 deletions

View File

@@ -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 {

View File

@@ -1968,6 +1968,21 @@ public final class Settings {
public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE =
"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>

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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();
}
}
}

View File

@@ -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 &amp; 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>&lt;{@link
* android.R.styleable#QuickAccessWalletService quickaccesswallet-service}&gt;</code> tag. This
* is a a sample XML file configuring an QuickAccessWalletService:
* <pre> &lt;quickaccesswallet-service
* android:walletActivity="foo.bar.WalletActivity"
* . . .
* /&gt;</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");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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"

View File

@@ -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.

View File

@@ -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" />