1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.quickaccesswallet;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.PendingIntent;
23 import android.graphics.drawable.Icon;
24 import android.location.Location;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.text.TextUtils;
28 
29 import com.android.internal.util.Preconditions;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 /**
37  * A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit
38  * card, library card, transit pass, etc. Cards are identified by a String identifier and contain a
39  * card type, card image, card image content description, and a {@link PendingIntent} to be used if
40  * the user clicks on the card. Cards may be displayed with an icon and label, though these are
41  * optional. Non-payment cards will also have a second image that will be displayed when the card is
42  * tapped.
43  */
44 
45 public final class WalletCard implements Parcelable {
46 
47     /**
48      * Unknown cards refer to cards whose types are unspecified.
49      */
50     public static final int CARD_TYPE_UNKNOWN = 0;
51 
52     /**
53      * Payment cards refer to credit cards, debit cards or any other cards in the wallet used to
54      * make cash-equivalent payments.
55      */
56     public static final int CARD_TYPE_PAYMENT = 1;
57 
58     /**
59      * Non-payment cards refer to any cards that are not used for cash-equivalent payment, including
60      * event tickets, flights, offers, loyalty cards, gift cards and transit tickets.
61      */
62     public static final int CARD_TYPE_NON_PAYMENT = 2;
63 
64     private final String mCardId;
65     private final int mCardType;
66     private final Icon mCardImage;
67     private final CharSequence mContentDescription;
68     private final PendingIntent mPendingIntent;
69     private final Icon mCardIcon;
70     private final CharSequence mCardLabel;
71     private final Icon mNonPaymentCardSecondaryImage;
72     private List<Location> mCardLocations;
73 
WalletCard(Builder builder)74     private WalletCard(Builder builder) {
75         this.mCardId = builder.mCardId;
76         this.mCardType = builder.mCardType;
77         this.mCardImage = builder.mCardImage;
78         this.mContentDescription = builder.mContentDescription;
79         this.mPendingIntent = builder.mPendingIntent;
80         this.mCardIcon = builder.mCardIcon;
81         this.mCardLabel = builder.mCardLabel;
82         this.mNonPaymentCardSecondaryImage = builder.mNonPaymentCardSecondaryImage;
83         this.mCardLocations = builder.mCardLocations;
84     }
85 
86     /**
87      * @hide
88      */
89     @Retention(RetentionPolicy.SOURCE)
90     @IntDef(prefix = {"CARD_TYPE_"}, value = {
91             CARD_TYPE_UNKNOWN,
92             CARD_TYPE_PAYMENT,
93             CARD_TYPE_NON_PAYMENT
94     })
95     public @interface CardType {
96     }
97 
98     @Override
describeContents()99     public int describeContents() {
100         return 0;
101     }
102 
103     @Override
writeToParcel(@onNull Parcel dest, int flags)104     public void writeToParcel(@NonNull Parcel dest, int flags) {
105         dest.writeString(mCardId);
106         dest.writeInt(mCardType);
107         mCardImage.writeToParcel(dest, flags);
108         TextUtils.writeToParcel(mContentDescription, dest, flags);
109         PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
110         writeIconIfNonNull(mCardIcon, dest, flags);
111         TextUtils.writeToParcel(mCardLabel, dest, flags);
112         writeIconIfNonNull(mNonPaymentCardSecondaryImage, dest, flags);
113         dest.writeTypedList(mCardLocations, flags);
114     }
115 
116     /** Utility function called by writeToParcel
117      */
writeIconIfNonNull(Icon icon, Parcel dest, int flags)118     private void writeIconIfNonNull(Icon icon,  Parcel dest, int flags) {
119         if (icon == null) {
120             dest.writeByte((byte) 0);
121         } else {
122             dest.writeByte((byte) 1);
123             icon.writeToParcel(dest, flags);
124         }
125     }
126 
readFromParcel(Parcel source)127     private static WalletCard readFromParcel(Parcel source) {
128         String cardId = source.readString();
129         int cardType = source.readInt();
130         Icon cardImage = Icon.CREATOR.createFromParcel(source);
131         CharSequence contentDesc = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
132         PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source);
133         Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
134         CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
135         Icon nonPaymentCardSecondaryImage =
136                 source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
137         Builder builder =
138                 new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent)
139                         .setCardIcon(cardIcon)
140                         .setCardLabel(cardLabel);
141         if (cardType == CARD_TYPE_NON_PAYMENT) {
142             builder.setNonPaymentCardSecondaryImage(nonPaymentCardSecondaryImage);
143         }
144         List<Location> cardLocations = new ArrayList<>();
145         source.readTypedList(cardLocations, Location.CREATOR);
146         builder.setCardLocations(cardLocations);
147 
148         return builder.build();
149     }
150 
151     @NonNull
152     public static final Creator<WalletCard> CREATOR =
153             new Creator<WalletCard>() {
154                 @Override
155                 public WalletCard createFromParcel(Parcel source) {
156                     return readFromParcel(source);
157                 }
158 
159                 @Override
160                 public WalletCard[] newArray(int size) {
161                     return new WalletCard[size];
162                 }
163             };
164 
165     /**
166      * The card id must be unique within the list of cards returned.
167      */
168     @NonNull
getCardId()169     public String getCardId() {
170         return mCardId;
171     }
172 
173     /**
174      * Returns the card type.
175      */
176     @NonNull
177     @CardType
getCardType()178     public int getCardType() {
179         return mCardType;
180     }
181 
182     /**
183      * The visual representation of the card. If the card image Icon is a bitmap, it should have a
184      * width of {@link GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
185      * GetWalletCardsRequest#getCardHeightPx()}.
186      */
187     @NonNull
getCardImage()188     public Icon getCardImage() {
189         return mCardImage;
190     }
191 
192     /**
193      * The content description of the card image.
194      */
195     @NonNull
getContentDescription()196     public CharSequence getContentDescription() {
197         return mContentDescription;
198     }
199 
200     /**
201      * If the user performs a click on the card, this PendingIntent will be sent. If the device is
202      * locked, the wallet will first request device unlock before sending the pending intent.
203      */
204     @NonNull
getPendingIntent()205     public PendingIntent getPendingIntent() {
206         return mPendingIntent;
207     }
208 
209     /**
210      * An icon may be shown alongside the card image to convey information about how the card can be
211      * used, or if some other action must be taken before using the card. For example, an NFC logo
212      * could indicate that the card is NFC-enabled and will be provided to an NFC terminal if the
213      * phone is held in close proximity to the NFC reader.
214      *
215      * <p>If the supplied Icon is backed by a bitmap, it should have width and height
216      * {@link GetWalletCardsRequest#getIconSizePx()}.
217      */
218     @Nullable
getCardIcon()219     public Icon getCardIcon() {
220         return mCardIcon;
221     }
222 
223     /**
224      * A card label may be shown alongside the card image to convey information about how the card
225      * can be used, or if some other action must be taken before using the card. For example, an
226      * NFC-enabled card could be labeled "Hold near reader" to inform the user of how to use NFC
227      * cards when interacting with an NFC reader.
228      *
229      * <p>If the provided label is too long to fit on one line, it may be truncated and ellipsized.
230      */
231     @Nullable
getCardLabel()232     public CharSequence getCardLabel() {
233         return mCardLabel;
234     }
235 
236     /**
237      * Visual representation of the card when it is tapped. May include additional information
238      * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
239      */
240     @Nullable
getNonPaymentCardSecondaryImage()241     public Icon getNonPaymentCardSecondaryImage() {
242         return mNonPaymentCardSecondaryImage;
243     }
244 
245     /** List of locations that this card might be useful at. */
246     @NonNull
getCardLocations()247     public List<Location> getCardLocations() {
248         return mCardLocations;
249     }
250 
251     /**
252      * Removes locations from card. Should be called if {@link
253      * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is disabled.
254      *
255      * @hide
256      */
removeCardLocations()257     public void removeCardLocations() {
258         mCardLocations = new ArrayList<>();
259     }
260 
261     /**
262      * Builder for {@link WalletCard} objects. You must provide cardId, cardImage,
263      * contentDescription, and pendingIntent. If the card is opaque and should be shown with
264      * elevation, set hasShadow to true. cardIcon and cardLabel are optional.
265      */
266     public static final class Builder {
267         private String mCardId;
268         private int mCardType;
269         private Icon mCardImage;
270         private CharSequence mContentDescription;
271         private PendingIntent mPendingIntent;
272         private Icon mCardIcon;
273         private CharSequence mCardLabel;
274         private Icon mNonPaymentCardSecondaryImage;
275         private List<Location> mCardLocations = new ArrayList<>();
276 
277         /**
278          * @param cardId             The card id must be non-null and unique within the list of
279          *                           cards returned. <b>Note:
280          *                           </b> this card ID should <b>not</b> contain PII (Personally
281          *                           Identifiable Information, such as username or email address).
282          * @param cardType           Integer representing the card type. The card type must be
283          *                           non-null.
284          * @param cardImage          The visual representation of the card. If the card image Icon
285          *                           is a bitmap, it should have a width of {@link
286          *                           GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
287          *                           GetWalletCardsRequest#getCardHeightPx()}. If the card image
288          *                           does not have these dimensions, it may appear distorted when it
289          *                           is scaled to fit these dimensions on screen. Bitmaps must be
290          *                           of type {@link android.graphics.Bitmap.Config#HARDWARE} for
291          *                           performance reasons.
292          * @param contentDescription The content description of the card image. This field is
293          *                           required and may not be null or empty.
294          *                           <b>Note: </b> this message should <b>not</b> contain PII
295          *                           (Personally Identifiable Information, such as username or email
296          *                           address).
297          * @param pendingIntent      If the user performs a click on the card, this PendingIntent
298          *                           will be sent. If the device is locked, the wallet will first
299          *                           request device unlock before sending the pending intent. It is
300          *                           recommended that the pending intent be immutable (use {@link
301          *                           PendingIntent#FLAG_IMMUTABLE}).
302          *
303          */
Builder(@onNull String cardId, @NonNull @CardType int cardType, @NonNull Icon cardImage, @NonNull CharSequence contentDescription, @NonNull PendingIntent pendingIntent )304         public Builder(@NonNull String cardId,
305                 @NonNull @CardType int cardType,
306                 @NonNull Icon cardImage,
307                 @NonNull CharSequence contentDescription,
308                 @NonNull PendingIntent pendingIntent
309         ) {
310             mCardId = cardId;
311             mCardType = cardType;
312             mCardImage = cardImage;
313             mContentDescription = contentDescription;
314             mPendingIntent = pendingIntent;
315         }
316 
317         /**
318          * Called when a card type is not provided, in which case it defaults to CARD_TYPE_UNKNOWN.
319          * Calls {@link Builder#Builder(String, int, Icon, CharSequence, PendingIntent)}
320          */
Builder(@onNull String cardId, @NonNull Icon cardImage, @NonNull CharSequence contentDescription, @NonNull PendingIntent pendingIntent)321         public Builder(@NonNull String cardId,
322                 @NonNull Icon cardImage,
323                 @NonNull CharSequence contentDescription,
324                 @NonNull PendingIntent pendingIntent) {
325             this(cardId, WalletCard.CARD_TYPE_UNKNOWN, cardImage, contentDescription,
326                     pendingIntent);
327         }
328 
329         /**
330          * An icon may be shown alongside the card image to convey information about how the card
331          * can be used, or if some other action must be taken before using the card. For example, an
332          * NFC logo could indicate that the card is NFC-enabled and will be provided to an NFC
333          * terminal if the phone is held in close proximity to the NFC reader. This field is
334          * optional.
335          *
336          * <p>If the supplied Icon is backed by a bitmap, it should have width and height
337          * {@link GetWalletCardsRequest#getIconSizePx()}.
338          */
339         @NonNull
setCardIcon(@ullable Icon cardIcon)340         public Builder setCardIcon(@Nullable Icon cardIcon) {
341             mCardIcon = cardIcon;
342             return this;
343         }
344 
345         /**
346          * A card label may be shown alongside the card image to convey information about how the
347          * card can be used, or if some other action must be taken before using the card. For
348          * example, an NFC-enabled card could be labeled "Hold near reader" to inform the user of
349          * how to use NFC cards when interacting with an NFC reader. This field is optional.
350          * <b>Note: </b> this card label should <b>not</b> contain PII (Personally Identifiable
351          * Information, such as username or email address). If the provided label is too long to fit
352          * on one line, it may be truncated and ellipsized.
353          */
354         @NonNull
setCardLabel(@ullable CharSequence cardLabel)355         public Builder setCardLabel(@Nullable CharSequence cardLabel) {
356             mCardLabel = cardLabel;
357             return this;
358         }
359 
360         /**
361          * Visual representation of the card when it is tapped. May include additional information
362          * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
363          */
364         @NonNull
setNonPaymentCardSecondaryImage( @ullable Icon nonPaymentCardSecondaryImage)365         public Builder setNonPaymentCardSecondaryImage(
366                 @Nullable Icon nonPaymentCardSecondaryImage) {
367             Preconditions.checkState(
368                     mCardType == CARD_TYPE_NON_PAYMENT,
369                     "This field can only be set on non-payment cards");
370             mNonPaymentCardSecondaryImage = nonPaymentCardSecondaryImage;
371             return this;
372         }
373 
374         /**
375          * Set of locations this card might be useful at. If {@link
376          * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be
377          * shown to the user when a user is near one of these locations.
378          */
379         @NonNull
setCardLocations(@onNull List<Location> cardLocations)380         public Builder setCardLocations(@NonNull List<Location> cardLocations) {
381             Preconditions.checkCollectionElementsNotNull(cardLocations, "cardLocations");
382             mCardLocations = cardLocations;
383             return this;
384         }
385 
386         /**
387          * Builds a new {@link WalletCard} instance.
388          *
389          * @return A built response.
390          */
391         @NonNull
build()392         public WalletCard build() {
393             return new WalletCard(this);
394         }
395     }
396 }
397