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:
@@ -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_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_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_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_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_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
|
||||||
field public static final String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
|
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_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_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_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_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_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";
|
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 {
|
package android.service.quicksettings {
|
||||||
|
|
||||||
public final class Tile implements android.os.Parcelable {
|
public final class Tile implements android.os.Parcelable {
|
||||||
|
|||||||
@@ -1968,6 +1968,21 @@ public final class Settings {
|
|||||||
public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE =
|
public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE =
|
||||||
"android.settings.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.
|
* Activity Action: Show screen for controlling which apps have access on volume directories.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
core/java/android/service/quickaccesswallet/WalletCard.aidl
Normal file
19
core/java/android/service/quickaccesswallet/WalletCard.aidl
Normal 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;
|
||||||
245
core/java/android/service/quickaccesswallet/WalletCard.java
Normal file
245
core/java/android/service/quickaccesswallet/WalletCard.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3226,6 +3226,13 @@
|
|||||||
<permission android:name="android.permission.BIND_NFC_SERVICE"
|
<permission android:name="android.permission.BIND_NFC_SERVICE"
|
||||||
android:protectionLevel="signature" />
|
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.
|
<!-- Must be required by the PrintSpooler to ensure that only the system can bind to it.
|
||||||
@hide -->
|
@hide -->
|
||||||
<permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
|
<permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
|
||||||
|
|||||||
@@ -8329,6 +8329,26 @@
|
|||||||
<attr name="successColor" format="color|reference"/>
|
<attr name="successColor" format="color|reference"/>
|
||||||
</declare-styleable>
|
</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
|
<!-- Use <code>recognition-service</code> as the root tag of the XML resource that
|
||||||
describes a {@link android.speech.RecognitionService}, which is referenced from
|
describes a {@link android.speech.RecognitionService}, which is referenced from
|
||||||
its {@link android.speech.RecognitionService#SERVICE_META_DATA} meta-data entry.
|
its {@link android.speech.RecognitionService#SERVICE_META_DATA} meta-data entry.
|
||||||
|
|||||||
@@ -174,6 +174,9 @@
|
|||||||
<!-- Adding Quick Settings tiles -->
|
<!-- Adding Quick Settings tiles -->
|
||||||
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
|
<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 -->
|
<!-- Adding Controls to SystemUI -->
|
||||||
<uses-permission android:name="android.permission.BIND_CONTROLS" />
|
<uses-permission android:name="android.permission.BIND_CONTROLS" />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user